drnic-haml 2.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.yardopts +5 -0
- data/CONTRIBUTING +4 -0
- data/MIT-LICENSE +20 -0
- data/README.md +347 -0
- data/REVISION +1 -0
- data/Rakefile +371 -0
- data/VERSION +1 -0
- data/VERSION_NAME +1 -0
- data/bin/css2sass +7 -0
- data/bin/haml +9 -0
- data/bin/html2haml +7 -0
- data/bin/sass +8 -0
- data/extra/haml-mode.el +663 -0
- data/extra/sass-mode.el +205 -0
- data/extra/update_watch.rb +13 -0
- data/init.rb +8 -0
- data/lib/haml.rb +40 -0
- data/lib/haml/buffer.rb +307 -0
- data/lib/haml/engine.rb +301 -0
- data/lib/haml/error.rb +22 -0
- data/lib/haml/exec.rb +470 -0
- data/lib/haml/filters.rb +341 -0
- data/lib/haml/helpers.rb +560 -0
- data/lib/haml/helpers/action_view_extensions.rb +40 -0
- data/lib/haml/helpers/action_view_mods.rb +176 -0
- data/lib/haml/herb.rb +96 -0
- data/lib/haml/html.rb +308 -0
- data/lib/haml/precompiler.rb +997 -0
- data/lib/haml/shared.rb +78 -0
- data/lib/haml/template.rb +51 -0
- data/lib/haml/template/patch.rb +58 -0
- data/lib/haml/template/plugin.rb +71 -0
- data/lib/haml/util.rb +244 -0
- data/lib/haml/version.rb +64 -0
- data/lib/sass.rb +24 -0
- data/lib/sass/css.rb +423 -0
- data/lib/sass/engine.rb +491 -0
- data/lib/sass/environment.rb +79 -0
- data/lib/sass/error.rb +162 -0
- data/lib/sass/files.rb +133 -0
- data/lib/sass/plugin.rb +170 -0
- data/lib/sass/plugin/merb.rb +57 -0
- data/lib/sass/plugin/rails.rb +23 -0
- data/lib/sass/repl.rb +58 -0
- data/lib/sass/script.rb +55 -0
- data/lib/sass/script/bool.rb +17 -0
- data/lib/sass/script/color.rb +183 -0
- data/lib/sass/script/funcall.rb +50 -0
- data/lib/sass/script/functions.rb +199 -0
- data/lib/sass/script/lexer.rb +191 -0
- data/lib/sass/script/literal.rb +177 -0
- data/lib/sass/script/node.rb +14 -0
- data/lib/sass/script/number.rb +381 -0
- data/lib/sass/script/operation.rb +45 -0
- data/lib/sass/script/parser.rb +222 -0
- data/lib/sass/script/string.rb +12 -0
- data/lib/sass/script/unary_operation.rb +34 -0
- data/lib/sass/script/variable.rb +31 -0
- data/lib/sass/tree/comment_node.rb +84 -0
- data/lib/sass/tree/debug_node.rb +30 -0
- data/lib/sass/tree/directive_node.rb +70 -0
- data/lib/sass/tree/for_node.rb +48 -0
- data/lib/sass/tree/if_node.rb +54 -0
- data/lib/sass/tree/import_node.rb +69 -0
- data/lib/sass/tree/mixin_def_node.rb +29 -0
- data/lib/sass/tree/mixin_node.rb +48 -0
- data/lib/sass/tree/node.rb +252 -0
- data/lib/sass/tree/prop_node.rb +106 -0
- data/lib/sass/tree/root_node.rb +56 -0
- data/lib/sass/tree/rule_node.rb +220 -0
- data/lib/sass/tree/variable_node.rb +34 -0
- data/lib/sass/tree/while_node.rb +31 -0
- data/rails/init.rb +1 -0
- data/test/benchmark.rb +99 -0
- data/test/haml/engine_test.rb +1129 -0
- data/test/haml/helper_test.rb +282 -0
- data/test/haml/html2haml_test.rb +258 -0
- data/test/haml/markaby/standard.mab +52 -0
- data/test/haml/mocks/article.rb +6 -0
- data/test/haml/results/content_for_layout.xhtml +12 -0
- data/test/haml/results/eval_suppressed.xhtml +9 -0
- data/test/haml/results/filters.xhtml +62 -0
- data/test/haml/results/helpers.xhtml +93 -0
- data/test/haml/results/helpful.xhtml +10 -0
- data/test/haml/results/just_stuff.xhtml +68 -0
- data/test/haml/results/list.xhtml +12 -0
- data/test/haml/results/nuke_inner_whitespace.xhtml +40 -0
- data/test/haml/results/nuke_outer_whitespace.xhtml +148 -0
- data/test/haml/results/original_engine.xhtml +20 -0
- data/test/haml/results/partial_layout.xhtml +5 -0
- data/test/haml/results/partials.xhtml +21 -0
- data/test/haml/results/render_layout.xhtml +3 -0
- data/test/haml/results/silent_script.xhtml +74 -0
- data/test/haml/results/standard.xhtml +162 -0
- data/test/haml/results/tag_parsing.xhtml +23 -0
- data/test/haml/results/very_basic.xhtml +5 -0
- data/test/haml/results/whitespace_handling.xhtml +89 -0
- data/test/haml/rhtml/_av_partial_1.rhtml +12 -0
- data/test/haml/rhtml/_av_partial_2.rhtml +8 -0
- data/test/haml/rhtml/action_view.rhtml +62 -0
- data/test/haml/rhtml/standard.rhtml +54 -0
- data/test/haml/spec_test.rb +44 -0
- data/test/haml/template_test.rb +217 -0
- data/test/haml/templates/_av_partial_1.haml +9 -0
- data/test/haml/templates/_av_partial_1_ugly.haml +9 -0
- data/test/haml/templates/_av_partial_2.haml +5 -0
- data/test/haml/templates/_av_partial_2_ugly.haml +5 -0
- data/test/haml/templates/_layout.erb +3 -0
- data/test/haml/templates/_layout_for_partial.haml +3 -0
- data/test/haml/templates/_partial.haml +8 -0
- data/test/haml/templates/_text_area.haml +3 -0
- data/test/haml/templates/action_view.haml +47 -0
- data/test/haml/templates/action_view_ugly.haml +47 -0
- data/test/haml/templates/breakage.haml +8 -0
- data/test/haml/templates/content_for_layout.haml +8 -0
- data/test/haml/templates/eval_suppressed.haml +11 -0
- data/test/haml/templates/filters.haml +66 -0
- data/test/haml/templates/helpers.haml +95 -0
- data/test/haml/templates/helpful.haml +11 -0
- data/test/haml/templates/just_stuff.haml +83 -0
- data/test/haml/templates/list.haml +12 -0
- data/test/haml/templates/nuke_inner_whitespace.haml +32 -0
- data/test/haml/templates/nuke_outer_whitespace.haml +144 -0
- data/test/haml/templates/original_engine.haml +17 -0
- data/test/haml/templates/partial_layout.haml +3 -0
- data/test/haml/templates/partialize.haml +1 -0
- data/test/haml/templates/partials.haml +12 -0
- data/test/haml/templates/render_layout.haml +2 -0
- data/test/haml/templates/silent_script.haml +40 -0
- data/test/haml/templates/standard.haml +42 -0
- data/test/haml/templates/standard_ugly.haml +42 -0
- data/test/haml/templates/tag_parsing.haml +21 -0
- data/test/haml/templates/very_basic.haml +4 -0
- data/test/haml/templates/whitespace_handling.haml +87 -0
- data/test/haml/util_test.rb +92 -0
- data/test/linked_rails.rb +12 -0
- data/test/sass/css2sass_test.rb +294 -0
- data/test/sass/engine_test.rb +956 -0
- data/test/sass/functions_test.rb +126 -0
- data/test/sass/more_results/more1.css +9 -0
- data/test/sass/more_results/more1_with_line_comments.css +26 -0
- data/test/sass/more_results/more_import.css +29 -0
- data/test/sass/more_templates/_more_partial.sass +2 -0
- data/test/sass/more_templates/more1.sass +23 -0
- data/test/sass/more_templates/more_import.sass +11 -0
- data/test/sass/plugin_test.rb +229 -0
- data/test/sass/results/alt.css +4 -0
- data/test/sass/results/basic.css +9 -0
- data/test/sass/results/compact.css +5 -0
- data/test/sass/results/complex.css +87 -0
- data/test/sass/results/compressed.css +1 -0
- data/test/sass/results/expanded.css +19 -0
- data/test/sass/results/import.css +29 -0
- data/test/sass/results/line_numbers.css +49 -0
- data/test/sass/results/mixins.css +95 -0
- data/test/sass/results/multiline.css +24 -0
- data/test/sass/results/nested.css +22 -0
- data/test/sass/results/parent_ref.css +13 -0
- data/test/sass/results/script.css +16 -0
- data/test/sass/results/subdir/nested_subdir/nested_subdir.css +1 -0
- data/test/sass/results/subdir/subdir.css +3 -0
- data/test/sass/results/units.css +11 -0
- data/test/sass/script_test.rb +261 -0
- data/test/sass/templates/_partial.sass +2 -0
- data/test/sass/templates/alt.sass +16 -0
- data/test/sass/templates/basic.sass +23 -0
- data/test/sass/templates/bork1.sass +2 -0
- data/test/sass/templates/bork2.sass +2 -0
- data/test/sass/templates/bork3.sass +2 -0
- data/test/sass/templates/compact.sass +17 -0
- data/test/sass/templates/complex.sass +307 -0
- data/test/sass/templates/compressed.sass +15 -0
- data/test/sass/templates/expanded.sass +17 -0
- data/test/sass/templates/import.sass +11 -0
- data/test/sass/templates/importee.sass +19 -0
- data/test/sass/templates/line_numbers.sass +13 -0
- data/test/sass/templates/mixins.sass +76 -0
- data/test/sass/templates/multiline.sass +20 -0
- data/test/sass/templates/nested.sass +25 -0
- data/test/sass/templates/nested_bork1.sass +2 -0
- data/test/sass/templates/nested_bork2.sass +2 -0
- data/test/sass/templates/nested_bork3.sass +2 -0
- data/test/sass/templates/parent_ref.sass +25 -0
- data/test/sass/templates/script.sass +101 -0
- data/test/sass/templates/subdir/nested_subdir/_nested_partial.sass +2 -0
- data/test/sass/templates/subdir/nested_subdir/nested_subdir.sass +3 -0
- data/test/sass/templates/subdir/subdir.sass +6 -0
- data/test/sass/templates/units.sass +11 -0
- data/test/test_helper.rb +44 -0
- metadata +298 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
module Sass::Script
|
2
|
+
# The abstract superclass for SassScript parse tree nodes.
|
3
|
+
#
|
4
|
+
# Use \{#perform} to evaluate a parse tree.
|
5
|
+
class Node
|
6
|
+
# Evaluates the node.
|
7
|
+
#
|
8
|
+
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
|
9
|
+
# @return [Literal] The SassScript object that is the value of the SassScript
|
10
|
+
def perform(environment)
|
11
|
+
raise NotImplementedError.new("All subclasses of Sass::Script::Node must override #perform.")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,381 @@
|
|
1
|
+
require 'sass/script/literal'
|
2
|
+
|
3
|
+
module Sass::Script
|
4
|
+
# A SassScript object representing a number.
|
5
|
+
# SassScript numbers can have decimal values,
|
6
|
+
# and can also have units.
|
7
|
+
# For example, `12`, `1px`, and `10.45em`
|
8
|
+
# are all valid values.
|
9
|
+
#
|
10
|
+
# Numbers can also have more complex units, such as `1px*em/in`.
|
11
|
+
# These cannot be inputted directly in Sass code at the moment.
|
12
|
+
class Number < Literal
|
13
|
+
# The Ruby value of the number.
|
14
|
+
#
|
15
|
+
# @return [Numeric]
|
16
|
+
attr_reader :value
|
17
|
+
|
18
|
+
# A list of units in the numerator of the number.
|
19
|
+
# For example, `1px*em/in*cm` would return `["px", "em"]`
|
20
|
+
# @return [Array<String>]
|
21
|
+
attr_reader :numerator_units
|
22
|
+
|
23
|
+
# A list of units in the denominator of the number.
|
24
|
+
# For example, `1px*em/in*cm` would return `["in", "cm"]`
|
25
|
+
# @return [Array<String>]
|
26
|
+
attr_reader :denominator_units
|
27
|
+
|
28
|
+
# The precision with which numbers will be printed to CSS files.
|
29
|
+
# For example, if this is `1000.0`,
|
30
|
+
# `3.1415926` will be printed as `3.142`.
|
31
|
+
PRECISION = 1000.0
|
32
|
+
|
33
|
+
# @param value [Numeric] The value of the number
|
34
|
+
# @param numerator_units [Array<String>] See \{#numerator\_units}
|
35
|
+
# @param denominator_units [Array<String>] See \{#denominator\_units}
|
36
|
+
def initialize(value, numerator_units = [], denominator_units = [])
|
37
|
+
super(value)
|
38
|
+
@numerator_units = numerator_units
|
39
|
+
@denominator_units = denominator_units
|
40
|
+
normalize!
|
41
|
+
end
|
42
|
+
|
43
|
+
# The SassScript `+` operation.
|
44
|
+
# Its functionality depends on the type of its argument:
|
45
|
+
#
|
46
|
+
# {Number}
|
47
|
+
# : Adds the two numbers together, converting units if possible.
|
48
|
+
#
|
49
|
+
# {Color}
|
50
|
+
# : Adds this number to each of the RGB color channels.
|
51
|
+
#
|
52
|
+
# {Literal}
|
53
|
+
# : See {Literal#plus}.
|
54
|
+
#
|
55
|
+
# @param other [Literal] The right-hand side of the operator
|
56
|
+
# @return [Literal] The result of the operation
|
57
|
+
# @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
|
58
|
+
def plus(other)
|
59
|
+
if other.is_a? Number
|
60
|
+
operate(other, :+)
|
61
|
+
elsif other.is_a?(Color)
|
62
|
+
other.plus(self)
|
63
|
+
else
|
64
|
+
super
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# The SassScript binary `-` operation (e.g. `!a - !b`).
|
69
|
+
# Its functionality depends on the type of its argument:
|
70
|
+
#
|
71
|
+
# {Number}
|
72
|
+
# : Subtracts this number from the other, converting units if possible.
|
73
|
+
#
|
74
|
+
# {Literal}
|
75
|
+
# : See {Literal#minus}.
|
76
|
+
#
|
77
|
+
# @param other [Literal] The right-hand side of the operator
|
78
|
+
# @return [Literal] The result of the operation
|
79
|
+
# @raise [Sass::UnitConversionError] if `other` is a number with incompatible units
|
80
|
+
def minus(other)
|
81
|
+
if other.is_a? Number
|
82
|
+
operate(other, :-)
|
83
|
+
else
|
84
|
+
super
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# The SassScript unary `-` operation (e.g. `-!a`).
|
89
|
+
#
|
90
|
+
# @return [Number] The negative value of this number
|
91
|
+
def unary_minus
|
92
|
+
Number.new(-value, numerator_units, denominator_units)
|
93
|
+
end
|
94
|
+
|
95
|
+
# The SassScript `*` operation.
|
96
|
+
# Its functionality depends on the type of its argument:
|
97
|
+
#
|
98
|
+
# {Number}
|
99
|
+
# : Multiplies the two numbers together, converting units appropriately.
|
100
|
+
#
|
101
|
+
# {Color}
|
102
|
+
# : Multiplies each of the RGB color channels by this number.
|
103
|
+
#
|
104
|
+
# @param other [Number, Color] The right-hand side of the operator
|
105
|
+
# @return [Number, Color] The result of the operation
|
106
|
+
# @raise [NoMethodError] if `other` is an invalid type
|
107
|
+
def times(other)
|
108
|
+
if other.is_a? Number
|
109
|
+
self.operate(other, :*)
|
110
|
+
elsif other.is_a? Color
|
111
|
+
other.times(self)
|
112
|
+
else
|
113
|
+
raise NoMethodError.new(nil, :times)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# The SassScript `/` operation.
|
118
|
+
# Its functionality depends on the type of its argument:
|
119
|
+
#
|
120
|
+
# {Number}
|
121
|
+
# : Divides this number by the other, converting units appropriately.
|
122
|
+
#
|
123
|
+
# {Literal}
|
124
|
+
# : See {Literal#div}.
|
125
|
+
#
|
126
|
+
# @param other [Literal] The right-hand side of the operator
|
127
|
+
# @return [Literal] The result of the operation
|
128
|
+
def div(other)
|
129
|
+
if other.is_a? Number
|
130
|
+
operate(other, :/)
|
131
|
+
else
|
132
|
+
super
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# The SassScript `%` operation.
|
137
|
+
#
|
138
|
+
# @param other [Number] The right-hand side of the operator
|
139
|
+
# @return [Number] This number modulo the other
|
140
|
+
# @raise [NoMethodError] if `other` is an invalid type
|
141
|
+
# @raise [Sass::UnitConversionError] if `other` has any units
|
142
|
+
def mod(other)
|
143
|
+
if other.is_a?(Number)
|
144
|
+
unless other.unitless?
|
145
|
+
raise Sass::UnitConversionError.new("Cannot modulo by a number with units: #{other.inspect}.")
|
146
|
+
end
|
147
|
+
operate(other, :%)
|
148
|
+
else
|
149
|
+
raise NoMethodError.new(nil, :mod)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# The SassScript `==` operation.
|
154
|
+
#
|
155
|
+
# @param other [Literal] The right-hand side of the operator
|
156
|
+
# @return [Boolean] Whether this number is equal to the other object
|
157
|
+
def eq(other)
|
158
|
+
return Sass::Script::Bool.new(false) unless other.is_a?(Sass::Script::Number)
|
159
|
+
this = self
|
160
|
+
begin
|
161
|
+
if unitless?
|
162
|
+
this = this.coerce(other.numerator_units, other.denominator_units)
|
163
|
+
else
|
164
|
+
other = other.coerce(numerator_units, denominator_units)
|
165
|
+
end
|
166
|
+
rescue Sass::UnitConversionError
|
167
|
+
return Sass::Script::Bool.new(false)
|
168
|
+
end
|
169
|
+
|
170
|
+
Sass::Script::Bool.new(this.value == other.value)
|
171
|
+
end
|
172
|
+
|
173
|
+
# The SassScript `>` operation.
|
174
|
+
#
|
175
|
+
# @param other [Number] The right-hand side of the operator
|
176
|
+
# @return [Boolean] Whether this number is greater than the other
|
177
|
+
# @raise [NoMethodError] if `other` is an invalid type
|
178
|
+
def gt(other)
|
179
|
+
raise NoMethodError.new(nil, :gt) unless other.is_a?(Number)
|
180
|
+
operate(other, :>)
|
181
|
+
end
|
182
|
+
|
183
|
+
# The SassScript `>=` operation.
|
184
|
+
#
|
185
|
+
# @param other [Number] The right-hand side of the operator
|
186
|
+
# @return [Boolean] Whether this number is greater than or equal to the other
|
187
|
+
# @raise [NoMethodError] if `other` is an invalid type
|
188
|
+
def gte(other)
|
189
|
+
raise NoMethodError.new(nil, :gte) unless other.is_a?(Number)
|
190
|
+
operate(other, :>=)
|
191
|
+
end
|
192
|
+
|
193
|
+
# The SassScript `<` operation.
|
194
|
+
#
|
195
|
+
# @param other [Number] The right-hand side of the operator
|
196
|
+
# @return [Boolean] Whether this number is less than the other
|
197
|
+
# @raise [NoMethodError] if `other` is an invalid type
|
198
|
+
def lt(other)
|
199
|
+
raise NoMethodError.new(nil, :lt) unless other.is_a?(Number)
|
200
|
+
operate(other, :<)
|
201
|
+
end
|
202
|
+
|
203
|
+
# The SassScript `<=` operation.
|
204
|
+
#
|
205
|
+
# @param other [Number] The right-hand side of the operator
|
206
|
+
# @return [Boolean] Whether this number is less than or equal to the other
|
207
|
+
# @raise [NoMethodError] if `other` is an invalid type
|
208
|
+
def lte(other)
|
209
|
+
raise NoMethodError.new(nil, :lte) unless other.is_a?(Number)
|
210
|
+
operate(other, :<=)
|
211
|
+
end
|
212
|
+
|
213
|
+
# @return [String] The CSS representation of this number
|
214
|
+
# @raise [Sass::SyntaxError] if this number has units that can't be used in CSS
|
215
|
+
# (e.g. `px*in`)
|
216
|
+
def to_s
|
217
|
+
raise Sass::SyntaxError.new("#{inspect} isn't a valid CSS value.") unless legal_units?
|
218
|
+
inspect
|
219
|
+
end
|
220
|
+
|
221
|
+
# Returns a readable representation of this number.
|
222
|
+
#
|
223
|
+
# This representation is valid CSS (and valid SassScript)
|
224
|
+
# as long as there is only one unit.
|
225
|
+
#
|
226
|
+
# @return [String] The representation
|
227
|
+
def inspect
|
228
|
+
value =
|
229
|
+
if self.value.is_a?(Float) && (self.value.infinite? || self.value.nan?)
|
230
|
+
self.value
|
231
|
+
elsif int?
|
232
|
+
self.value.to_i
|
233
|
+
else
|
234
|
+
(self.value * PRECISION).round / PRECISION
|
235
|
+
end
|
236
|
+
"#{value}#{unit_str}"
|
237
|
+
end
|
238
|
+
|
239
|
+
# @return [Fixnum] The integer value of the number
|
240
|
+
# @raise [Sass::SyntaxError] if the number isn't an integer
|
241
|
+
def to_i
|
242
|
+
super unless int?
|
243
|
+
return value
|
244
|
+
end
|
245
|
+
|
246
|
+
# @return [Boolean] Whether or not this number is an integer.
|
247
|
+
def int?
|
248
|
+
value % 1 == 0.0
|
249
|
+
end
|
250
|
+
|
251
|
+
# @return [Boolean] Whether or not this number has no units.
|
252
|
+
def unitless?
|
253
|
+
numerator_units.empty? && denominator_units.empty?
|
254
|
+
end
|
255
|
+
|
256
|
+
# @return [Boolean] Whether or not this number has units that can be represented in CSS
|
257
|
+
# (that is, zero or one \{#numerator\_units}).
|
258
|
+
def legal_units?
|
259
|
+
(numerator_units.empty? || numerator_units.size == 1) && denominator_units.empty?
|
260
|
+
end
|
261
|
+
|
262
|
+
# Returns this number converted to other units.
|
263
|
+
# The conversion takes into account the relationship between e.g. mm and cm,
|
264
|
+
# as well as between e.g. in and cm.
|
265
|
+
#
|
266
|
+
# If this number has no units, it will simply return itself
|
267
|
+
# with the given units.
|
268
|
+
#
|
269
|
+
# An incompatible coercion, e.g. between px and cm, will raise an error.
|
270
|
+
#
|
271
|
+
# @param num_units [Array<String>] The numerator units to coerce this number into.
|
272
|
+
# See {\#numerator\_units}
|
273
|
+
# @param den_units [Array<String>] The denominator units to coerce this number into.
|
274
|
+
# See {\#denominator\_units}
|
275
|
+
# @return [Number] The number with the new units
|
276
|
+
# @raise [Sass::UnitConversionError] if the given units are incompatible with the number's
|
277
|
+
# current units
|
278
|
+
def coerce(num_units, den_units)
|
279
|
+
Number.new(if unitless?
|
280
|
+
self.value
|
281
|
+
else
|
282
|
+
self.value * coercion_factor(self.numerator_units, num_units) /
|
283
|
+
coercion_factor(self.denominator_units, den_units)
|
284
|
+
end, num_units, den_units)
|
285
|
+
end
|
286
|
+
|
287
|
+
protected
|
288
|
+
|
289
|
+
def operate(other, operation)
|
290
|
+
this = self
|
291
|
+
if [:+, :-, :<=, :<, :>, :>=].include?(operation)
|
292
|
+
if unitless?
|
293
|
+
this = this.coerce(other.numerator_units, other.denominator_units)
|
294
|
+
else
|
295
|
+
other = other.coerce(numerator_units, denominator_units)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
# avoid integer division
|
299
|
+
value = (:/ == operation) ? this.value.to_f : this.value
|
300
|
+
result = value.send(operation, other.value)
|
301
|
+
|
302
|
+
if result.is_a?(Numeric)
|
303
|
+
Number.new(result, *compute_units(this, other, operation))
|
304
|
+
else # Boolean op
|
305
|
+
Bool.new(result)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def coercion_factor(from_units, to_units)
|
310
|
+
# get a list of unmatched units
|
311
|
+
from_units, to_units = sans_common_units(from_units, to_units)
|
312
|
+
|
313
|
+
if from_units.size != to_units.size || !convertable?(from_units | to_units)
|
314
|
+
raise Sass::UnitConversionError.new("Incompatible units: '#{from_units.join('*')}' and '#{to_units.join('*')}'.")
|
315
|
+
end
|
316
|
+
|
317
|
+
from_units.zip(to_units).inject(1) {|m,p| m * conversion_factor(p[0], p[1]) }
|
318
|
+
end
|
319
|
+
|
320
|
+
def compute_units(this, other, operation)
|
321
|
+
case operation
|
322
|
+
when :*
|
323
|
+
[this.numerator_units + other.numerator_units, this.denominator_units + other.denominator_units]
|
324
|
+
when :/
|
325
|
+
[this.numerator_units + other.denominator_units, this.denominator_units + other.numerator_units]
|
326
|
+
else
|
327
|
+
[this.numerator_units, this.denominator_units]
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def unit_str
|
332
|
+
rv = numerator_units.join("*")
|
333
|
+
if denominator_units.any?
|
334
|
+
rv << "/"
|
335
|
+
rv << denominator_units.join("*")
|
336
|
+
end
|
337
|
+
rv
|
338
|
+
end
|
339
|
+
|
340
|
+
def normalize!
|
341
|
+
return if unitless?
|
342
|
+
@numerator_units, @denominator_units = sans_common_units(numerator_units, denominator_units)
|
343
|
+
|
344
|
+
@denominator_units.each_with_index do |d, i|
|
345
|
+
if convertable?(d) && (u = @numerator_units.detect(&method(:convertable?)))
|
346
|
+
@value /= conversion_factor(d, u)
|
347
|
+
@denominator_units.delete_at(i)
|
348
|
+
@numerator_units.delete_at(@numerator_units.index(u))
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
# A hash of unit names to their index in the conversion table
|
354
|
+
CONVERTABLE_UNITS = {"in" => 0, "cm" => 1, "pc" => 2, "mm" => 3, "pt" => 4}
|
355
|
+
CONVERSION_TABLE = [[ 1, 2.54, 6, 25.4, 72 ], # in
|
356
|
+
[ nil, 1, 2.36220473, 10, 28.3464567], # cm
|
357
|
+
[ nil, nil, 1, 4.23333333, 12 ], # pc
|
358
|
+
[ nil, nil, nil, 1, 2.83464567], # mm
|
359
|
+
[ nil, nil, nil, nil, 1 ]] # pt
|
360
|
+
|
361
|
+
def conversion_factor(from_unit, to_unit)
|
362
|
+
res = CONVERSION_TABLE[CONVERTABLE_UNITS[from_unit]][CONVERTABLE_UNITS[to_unit]]
|
363
|
+
return 1.0 / conversion_factor(to_unit, from_unit) if res.nil?
|
364
|
+
res
|
365
|
+
end
|
366
|
+
|
367
|
+
def convertable?(units)
|
368
|
+
Array(units).all?(&CONVERTABLE_UNITS.method(:include?))
|
369
|
+
end
|
370
|
+
|
371
|
+
def sans_common_units(units1, units2)
|
372
|
+
units2 = units2.dup
|
373
|
+
# Can't just use -, because we want px*px to coerce properly to px*mm
|
374
|
+
return units1.map do |u|
|
375
|
+
next u unless j = units2.index(u)
|
376
|
+
units2.delete_at(j)
|
377
|
+
nil
|
378
|
+
end.compact, units2
|
379
|
+
end
|
380
|
+
end
|
381
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'sass/script/string'
|
3
|
+
require 'sass/script/number'
|
4
|
+
require 'sass/script/color'
|
5
|
+
require 'sass/script/functions'
|
6
|
+
require 'sass/script/unary_operation'
|
7
|
+
|
8
|
+
module Sass::Script
|
9
|
+
# A SassScript parse node representing a binary operation,
|
10
|
+
# such as `!a + !b` or `"foo" + 1`.
|
11
|
+
class Operation < Node
|
12
|
+
# @param operand1 [Script::Node] The parse-tree node
|
13
|
+
# for the right-hand side of the operator
|
14
|
+
# @param operand2 [Script::Node] The parse-tree node
|
15
|
+
# for the left-hand side of the operator
|
16
|
+
# @param operator [Symbol] The operator to perform.
|
17
|
+
# This should be one of the binary operator names in {Lexer::OPERATORS}
|
18
|
+
def initialize(operand1, operand2, operator)
|
19
|
+
@operand1 = operand1
|
20
|
+
@operand2 = operand2
|
21
|
+
@operator = operator
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [String] A human-readable s-expression representation of the operation
|
25
|
+
def inspect
|
26
|
+
"(#{@operator.inspect} #{@operand1.inspect} #{@operand2.inspect})"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Evaluates the operation.
|
30
|
+
#
|
31
|
+
# @param environment [Sass::Environment] The environment in which to evaluate the SassScript
|
32
|
+
# @return [Literal] The SassScript object that is the value of the operation
|
33
|
+
# @raise [Sass::SyntaxError] if the operation is undefined for the operands
|
34
|
+
def perform(environment)
|
35
|
+
literal1 = @operand1.perform(environment)
|
36
|
+
literal2 = @operand2.perform(environment)
|
37
|
+
begin
|
38
|
+
literal1.send(@operator, literal2)
|
39
|
+
rescue NoMethodError => e
|
40
|
+
raise e unless e.name.to_s == @operator.to_s
|
41
|
+
raise Sass::SyntaxError.new("Undefined operation: \"#{literal1} #{@operator} #{literal2}\".")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
require 'sass/script/lexer'
|
2
|
+
|
3
|
+
module Sass
|
4
|
+
module Script
|
5
|
+
# The parser for SassScript.
|
6
|
+
# It parses a string of code into a tree of {Script::Node}s.
|
7
|
+
class Parser
|
8
|
+
# @param str [String, StringScanner] The source text to parse
|
9
|
+
# @param line [Fixnum] The line on which the SassScript appears.
|
10
|
+
# Used for error reporting
|
11
|
+
# @param offset [Fixnum] The number of characters in on which the SassScript appears.
|
12
|
+
# 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)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Parses a SassScript expression within an interpolated segment (`#{}`).
|
21
|
+
# This means that it stops when it comes across an unmatched `}`,
|
22
|
+
# which signals the end of an interpolated segment,
|
23
|
+
# it returns rather than throwing an error.
|
24
|
+
#
|
25
|
+
# @return [Script::Node] The root node of the parse tree
|
26
|
+
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
|
27
|
+
def parse_interpolated
|
28
|
+
expr = assert_expr :expr
|
29
|
+
assert_tok :end_interpolation
|
30
|
+
expr
|
31
|
+
end
|
32
|
+
|
33
|
+
# Parses a SassScript expression.
|
34
|
+
#
|
35
|
+
# @return [Script::Node] The root node of the parse tree
|
36
|
+
# @raise [Sass::SyntaxError] if the expression isn't valid SassScript
|
37
|
+
def parse
|
38
|
+
expr = assert_expr :expr
|
39
|
+
assert_done
|
40
|
+
expr
|
41
|
+
end
|
42
|
+
|
43
|
+
# Parses the argument list for a mixin include.
|
44
|
+
#
|
45
|
+
# @return [Array<Script::Node>] The root nodes of the arguments.
|
46
|
+
# @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
|
47
|
+
def parse_mixin_include_arglist
|
48
|
+
args = []
|
49
|
+
|
50
|
+
if try_tok(:lparen)
|
51
|
+
args = arglist || args
|
52
|
+
assert_tok(:rparen)
|
53
|
+
end
|
54
|
+
assert_done
|
55
|
+
|
56
|
+
args
|
57
|
+
end
|
58
|
+
|
59
|
+
# Parses the argument list for a mixin definition.
|
60
|
+
#
|
61
|
+
# @return [Array<Script::Node>] The root nodes of the arguments.
|
62
|
+
# @raise [Sass::SyntaxError] if the argument list isn't valid SassScript
|
63
|
+
def parse_mixin_definition_arglist
|
64
|
+
args = []
|
65
|
+
|
66
|
+
if try_tok(:lparen)
|
67
|
+
args = defn_arglist(false) || args
|
68
|
+
assert_tok(:rparen)
|
69
|
+
end
|
70
|
+
assert_done
|
71
|
+
|
72
|
+
args
|
73
|
+
end
|
74
|
+
|
75
|
+
# Parses a SassScript expression.
|
76
|
+
#
|
77
|
+
# @overload parse(str, line, offset, filename = nil)
|
78
|
+
# @return [Script::Node] The root node of the parse tree
|
79
|
+
# @see Parser#initialize
|
80
|
+
# @see Parser#parse
|
81
|
+
def self.parse(*args)
|
82
|
+
new(*args).parse
|
83
|
+
end
|
84
|
+
|
85
|
+
class << self
|
86
|
+
private
|
87
|
+
|
88
|
+
# Defines a simple left-associative production.
|
89
|
+
# name is the name of the production,
|
90
|
+
# sub is the name of the production beneath it,
|
91
|
+
# and ops is a list of operators for this precedence level
|
92
|
+
def production(name, sub, *ops)
|
93
|
+
class_eval <<RUBY
|
94
|
+
def #{name}
|
95
|
+
return unless e = #{sub}
|
96
|
+
while tok = try_tok(#{ops.map {|o| o.inspect}.join(', ')})
|
97
|
+
e = Operation.new(e, assert_expr(#{sub.inspect}), tok.type)
|
98
|
+
end
|
99
|
+
e
|
100
|
+
end
|
101
|
+
RUBY
|
102
|
+
end
|
103
|
+
|
104
|
+
def unary(op, sub)
|
105
|
+
class_eval <<RUBY
|
106
|
+
def unary_#{op}
|
107
|
+
return #{sub} unless try_tok(:#{op})
|
108
|
+
UnaryOperation.new(assert_expr(:unary_#{op}), :#{op})
|
109
|
+
end
|
110
|
+
RUBY
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
production :expr, :concat, :comma
|
117
|
+
|
118
|
+
def concat
|
119
|
+
return unless e = or_expr
|
120
|
+
while sub = or_expr
|
121
|
+
e = Operation.new(e, sub, :concat)
|
122
|
+
end
|
123
|
+
e
|
124
|
+
end
|
125
|
+
|
126
|
+
production :or_expr, :and_expr, :or
|
127
|
+
production :and_expr, :eq_or_neq, :and
|
128
|
+
production :eq_or_neq, :relational, :eq, :neq
|
129
|
+
production :relational, :plus_or_minus, :gt, :gte, :lt, :lte
|
130
|
+
production :plus_or_minus, :times_div_or_mod, :plus, :minus
|
131
|
+
production :times_div_or_mod, :unary_minus, :times, :div, :mod
|
132
|
+
|
133
|
+
unary :minus, :unary_div
|
134
|
+
unary :div, :unary_not # For strings, so /foo/bar works
|
135
|
+
unary :not, :funcall
|
136
|
+
|
137
|
+
def funcall
|
138
|
+
return paren unless name = try_tok(:ident)
|
139
|
+
# An identifier without arguments is just a string
|
140
|
+
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 2.4.
|
145
|
+
'#{name.value}' was not quoted. Please add double quotes (e.g. "#{name.value}").
|
146
|
+
END
|
147
|
+
Script::String.new(name.value)
|
148
|
+
else
|
149
|
+
args = arglist || []
|
150
|
+
assert_tok(:rparen)
|
151
|
+
Script::Funcall.new(name.value, args)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def defn_arglist(must_have_default)
|
156
|
+
return unless c = try_tok(:const)
|
157
|
+
var = Script::Variable.new(c.value)
|
158
|
+
if try_tok(:single_eq)
|
159
|
+
val = assert_expr(:concat)
|
160
|
+
elsif must_have_default
|
161
|
+
raise SyntaxError.new("Required argument #{var.inspect} must come before any optional arguments.")
|
162
|
+
end
|
163
|
+
|
164
|
+
return [[var, val]] unless try_tok(:comma)
|
165
|
+
[[var, val], *defn_arglist(val)]
|
166
|
+
end
|
167
|
+
|
168
|
+
def arglist
|
169
|
+
return unless e = concat
|
170
|
+
return [e] unless try_tok(:comma)
|
171
|
+
[e, *arglist]
|
172
|
+
end
|
173
|
+
|
174
|
+
def paren
|
175
|
+
return variable unless try_tok(:lparen)
|
176
|
+
e = assert_expr(:expr)
|
177
|
+
assert_tok(:rparen)
|
178
|
+
return e
|
179
|
+
end
|
180
|
+
|
181
|
+
def variable
|
182
|
+
return string unless c = try_tok(:const)
|
183
|
+
Variable.new(c.value)
|
184
|
+
end
|
185
|
+
|
186
|
+
def string
|
187
|
+
return literal unless first = try_tok(:string)
|
188
|
+
return first.value unless try_tok(:begin_interpolation)
|
189
|
+
mid = parse_interpolated
|
190
|
+
last = assert_expr(:string)
|
191
|
+
Operation.new(first.value, Operation.new(mid, last, :plus), :plus)
|
192
|
+
end
|
193
|
+
|
194
|
+
def literal
|
195
|
+
(t = try_tok(:number, :color, :bool)) && (return t.value)
|
196
|
+
end
|
197
|
+
|
198
|
+
# It would be possible to have unified #assert and #try methods,
|
199
|
+
# but detecting the method/token difference turns out to be quite expensive.
|
200
|
+
|
201
|
+
def assert_expr(name)
|
202
|
+
(e = send(name)) && (return e)
|
203
|
+
raise Sass::SyntaxError.new("Expected expression, was #{@lexer.done? ? 'end of text' : "#{@lexer.peek.type} token"}.")
|
204
|
+
end
|
205
|
+
|
206
|
+
def assert_tok(*names)
|
207
|
+
(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"}.")
|
209
|
+
end
|
210
|
+
|
211
|
+
def try_tok(*names)
|
212
|
+
peeked = @lexer.peek
|
213
|
+
peeked && names.include?(peeked.type) && @lexer.next
|
214
|
+
end
|
215
|
+
|
216
|
+
def assert_done
|
217
|
+
return if @lexer.done?
|
218
|
+
raise Sass::SyntaxError.new("Unexpected #{@lexer.peek.type} token.")
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|