less 0.8.13 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. data/README.md +10 -1
  2. data/Rakefile +21 -3
  3. data/VERSION +1 -1
  4. data/bin/lessc +0 -8
  5. data/less.gemspec +138 -15
  6. data/lib/less.rb +67 -11
  7. data/lib/less/command.rb +24 -25
  8. data/lib/less/engine.rb +36 -140
  9. data/lib/less/engine/builder.rb +8 -0
  10. data/lib/less/engine/less.tt +374 -0
  11. data/lib/less/engine/nodes.rb +8 -0
  12. data/lib/less/engine/nodes/element.rb +150 -0
  13. data/lib/less/engine/nodes/entity.rb +73 -0
  14. data/lib/less/engine/nodes/function.rb +82 -0
  15. data/lib/less/engine/nodes/literal.rb +135 -0
  16. data/lib/less/engine/nodes/property.rb +112 -0
  17. data/lib/less/engine/nodes/selector.rb +39 -0
  18. data/lib/less/engine/parser.rb +3860 -0
  19. data/lib/vendor/treetop/.gitignore +7 -0
  20. data/lib/vendor/treetop/LICENSE +19 -0
  21. data/lib/vendor/treetop/README +164 -0
  22. data/lib/vendor/treetop/Rakefile +19 -0
  23. data/lib/vendor/treetop/benchmark/seqpar.gnuplot +15 -0
  24. data/lib/vendor/treetop/benchmark/seqpar.treetop +16 -0
  25. data/lib/vendor/treetop/benchmark/seqpar_benchmark.rb +107 -0
  26. data/lib/vendor/treetop/bin/tt +28 -0
  27. data/lib/vendor/treetop/lib/treetop.rb +8 -0
  28. data/lib/vendor/treetop/lib/treetop/bootstrap_gen_1_metagrammar.rb +45 -0
  29. data/lib/vendor/treetop/lib/treetop/compiler.rb +6 -0
  30. data/lib/vendor/treetop/lib/treetop/compiler/grammar_compiler.rb +42 -0
  31. data/lib/vendor/treetop/lib/treetop/compiler/lexical_address_space.rb +17 -0
  32. data/lib/vendor/treetop/lib/treetop/compiler/metagrammar.rb +3097 -0
  33. data/lib/vendor/treetop/lib/treetop/compiler/metagrammar.treetop +408 -0
  34. data/lib/vendor/treetop/lib/treetop/compiler/node_classes.rb +19 -0
  35. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/anything_symbol.rb +18 -0
  36. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/atomic_expression.rb +14 -0
  37. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/character_class.rb +23 -0
  38. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/choice.rb +31 -0
  39. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/declaration_sequence.rb +24 -0
  40. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/grammar.rb +28 -0
  41. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/inline_module.rb +27 -0
  42. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/nonterminal.rb +13 -0
  43. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/optional.rb +19 -0
  44. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/parenthesized_expression.rb +9 -0
  45. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/parsing_expression.rb +146 -0
  46. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/parsing_rule.rb +55 -0
  47. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/predicate.rb +45 -0
  48. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/repetition.rb +55 -0
  49. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/sequence.rb +68 -0
  50. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/terminal.rb +20 -0
  51. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/transient_prefix.rb +9 -0
  52. data/lib/vendor/treetop/lib/treetop/compiler/node_classes/treetop_file.rb +9 -0
  53. data/lib/vendor/treetop/lib/treetop/compiler/ruby_builder.rb +113 -0
  54. data/lib/vendor/treetop/lib/treetop/ruby_extensions.rb +2 -0
  55. data/lib/vendor/treetop/lib/treetop/ruby_extensions/string.rb +42 -0
  56. data/lib/vendor/treetop/lib/treetop/runtime.rb +5 -0
  57. data/lib/vendor/treetop/lib/treetop/runtime/compiled_parser.rb +109 -0
  58. data/lib/vendor/treetop/lib/treetop/runtime/interval_skip_list.rb +4 -0
  59. data/lib/vendor/treetop/lib/treetop/runtime/interval_skip_list/head_node.rb +15 -0
  60. data/lib/vendor/treetop/lib/treetop/runtime/interval_skip_list/interval_skip_list.rb +200 -0
  61. data/lib/vendor/treetop/lib/treetop/runtime/interval_skip_list/node.rb +164 -0
  62. data/lib/vendor/treetop/lib/treetop/runtime/syntax_node.rb +90 -0
  63. data/lib/vendor/treetop/lib/treetop/runtime/terminal_parse_failure.rb +16 -0
  64. data/lib/vendor/treetop/lib/treetop/runtime/terminal_syntax_node.rb +17 -0
  65. data/lib/vendor/treetop/lib/treetop/version.rb +9 -0
  66. data/lib/vendor/treetop/spec/compiler/and_predicate_spec.rb +36 -0
  67. data/lib/vendor/treetop/spec/compiler/anything_symbol_spec.rb +44 -0
  68. data/lib/vendor/treetop/spec/compiler/character_class_spec.rb +247 -0
  69. data/lib/vendor/treetop/spec/compiler/choice_spec.rb +80 -0
  70. data/lib/vendor/treetop/spec/compiler/circular_compilation_spec.rb +28 -0
  71. data/lib/vendor/treetop/spec/compiler/failure_propagation_functional_spec.rb +21 -0
  72. data/lib/vendor/treetop/spec/compiler/grammar_compiler_spec.rb +84 -0
  73. data/lib/vendor/treetop/spec/compiler/grammar_spec.rb +41 -0
  74. data/lib/vendor/treetop/spec/compiler/nonterminal_symbol_spec.rb +40 -0
  75. data/lib/vendor/treetop/spec/compiler/not_predicate_spec.rb +38 -0
  76. data/lib/vendor/treetop/spec/compiler/one_or_more_spec.rb +35 -0
  77. data/lib/vendor/treetop/spec/compiler/optional_spec.rb +37 -0
  78. data/lib/vendor/treetop/spec/compiler/parenthesized_expression_spec.rb +19 -0
  79. data/lib/vendor/treetop/spec/compiler/parsing_rule_spec.rb +32 -0
  80. data/lib/vendor/treetop/spec/compiler/sequence_spec.rb +115 -0
  81. data/lib/vendor/treetop/spec/compiler/terminal_spec.rb +81 -0
  82. data/lib/vendor/treetop/spec/compiler/terminal_symbol_spec.rb +37 -0
  83. data/lib/vendor/treetop/spec/compiler/test_grammar.treetop +7 -0
  84. data/lib/vendor/treetop/spec/compiler/test_grammar.tt +7 -0
  85. data/lib/vendor/treetop/spec/compiler/test_grammar_do.treetop +7 -0
  86. data/lib/vendor/treetop/spec/compiler/zero_or_more_spec.rb +56 -0
  87. data/lib/vendor/treetop/spec/composition/a.treetop +11 -0
  88. data/lib/vendor/treetop/spec/composition/b.treetop +11 -0
  89. data/lib/vendor/treetop/spec/composition/c.treetop +10 -0
  90. data/lib/vendor/treetop/spec/composition/d.treetop +10 -0
  91. data/lib/vendor/treetop/spec/composition/f.treetop +17 -0
  92. data/lib/vendor/treetop/spec/composition/grammar_composition_spec.rb +40 -0
  93. data/lib/vendor/treetop/spec/composition/subfolder/e_includes_c.treetop +15 -0
  94. data/lib/vendor/treetop/spec/ruby_extensions/string_spec.rb +32 -0
  95. data/lib/vendor/treetop/spec/runtime/compiled_parser_spec.rb +101 -0
  96. data/lib/vendor/treetop/spec/runtime/interval_skip_list/delete_spec.rb +147 -0
  97. data/lib/vendor/treetop/spec/runtime/interval_skip_list/expire_range_spec.rb +349 -0
  98. data/lib/vendor/treetop/spec/runtime/interval_skip_list/insert_and_delete_node.rb +385 -0
  99. data/lib/vendor/treetop/spec/runtime/interval_skip_list/insert_spec.rb +660 -0
  100. data/lib/vendor/treetop/spec/runtime/interval_skip_list/interval_skip_list_spec.graffle +6175 -0
  101. data/lib/vendor/treetop/spec/runtime/interval_skip_list/interval_skip_list_spec.rb +58 -0
  102. data/lib/vendor/treetop/spec/runtime/interval_skip_list/palindromic_fixture.rb +23 -0
  103. data/lib/vendor/treetop/spec/runtime/interval_skip_list/palindromic_fixture_spec.rb +164 -0
  104. data/lib/vendor/treetop/spec/runtime/interval_skip_list/spec_helper.rb +84 -0
  105. data/lib/vendor/treetop/spec/runtime/syntax_node_spec.rb +68 -0
  106. data/lib/vendor/treetop/spec/spec_helper.rb +106 -0
  107. data/lib/vendor/treetop/spec/spec_suite.rb +4 -0
  108. data/lib/vendor/treetop/treetop.gemspec +17 -0
  109. data/spec/command_spec.rb +2 -6
  110. data/spec/css/accessors-1.0.css +18 -0
  111. data/spec/css/big-1.0.css +3768 -0
  112. data/spec/css/comments-1.0.css +9 -0
  113. data/spec/css/css-1.0.css +40 -0
  114. data/spec/css/functions-1.0.css +6 -0
  115. data/spec/css/import-1.0.css +11 -0
  116. data/spec/css/mixins-1.0.css +28 -0
  117. data/spec/css/operations-1.0.css +28 -0
  118. data/spec/css/rulesets-1.0.css +17 -0
  119. data/spec/css/scope-1.0.css +14 -0
  120. data/spec/css/strings-1.0.css +12 -0
  121. data/spec/css/variables-1.0.css +6 -0
  122. data/spec/css/whitespace-1.0.css +9 -0
  123. data/spec/engine_spec.rb +66 -18
  124. data/spec/less/accessors-1.0.less +20 -0
  125. data/spec/less/big-1.0.less +4810 -0
  126. data/spec/less/colors-1.0.less +0 -0
  127. data/spec/less/comments-1.0.less +46 -0
  128. data/spec/less/css-1.0.less +84 -0
  129. data/spec/less/exceptions/mixed-units-error.less +3 -0
  130. data/spec/less/exceptions/name-error-1.0.less +3 -0
  131. data/spec/less/exceptions/syntax-error-1.0.less +3 -0
  132. data/spec/less/functions-1.0.less +6 -0
  133. data/spec/less/import-1.0.less +7 -0
  134. data/spec/less/import/import-test-a.less +2 -0
  135. data/spec/less/import/import-test-b.less +8 -0
  136. data/spec/less/import/import-test-c.less +5 -0
  137. data/spec/less/mixins-1.0.less +43 -0
  138. data/spec/less/operations-1.0.less +39 -0
  139. data/spec/less/rulesets-1.0.less +30 -0
  140. data/spec/less/scope-1.0.less +33 -0
  141. data/spec/less/strings-1.0.less +14 -0
  142. data/spec/less/variables-1.0.less +18 -0
  143. data/spec/less/whitespace-1.0.less +21 -0
  144. data/spec/spec.css +79 -24
  145. data/spec/spec.less +2 -3
  146. data/spec/spec_helper.rb +4 -1
  147. metadata +136 -13
  148. data/lib/less/tree.rb +0 -82
  149. data/spec/css/less-0.8.10.css +0 -30
  150. data/spec/css/less-0.8.11.css +0 -31
  151. data/spec/css/less-0.8.12.css +0 -28
  152. data/spec/css/less-0.8.5.css +0 -24
  153. data/spec/css/less-0.8.6.css +0 -24
  154. data/spec/css/less-0.8.7.css +0 -24
  155. data/spec/css/less-0.8.8.css +0 -25
  156. data/spec/tree_spec.rb +0 -5
@@ -1,154 +1,50 @@
1
- module Less
2
- class Engine < String
3
- # Compound properties, such as `border: 1px solid black`
4
- COMPOUND = {'font' => true, 'background' => false, 'border' => false }
5
- REGEX = {
6
- :path => /([#.][->#.\w ]+)?( ?> ?)?/, # #header > .title
7
- :selector => /[-\w #.,>*+:\(\)\=\[\]']/, # .cow .milk > a
8
- :variable => /@([-\w]+)/, # @milk-white
9
- :property => /@[-\w]+|[-a-z*0-9_]+/, # font-size
10
- :color => /#([a-zA-Z0-9]{3,6})\b/, # #f0f0f0
11
- :number => /\d+(?>\.\d+)?/, # 24.8
12
- :unit => /px|em|pt|cm|mm|%/ # em
13
- }
14
- REGEX[:numeric] = /#{REGEX[:number]}(#{REGEX[:unit]})?/
15
- REGEX[:operand] = /#{REGEX[:color]}|#{REGEX[:numeric]}/
16
-
17
- def initialize s
18
- super
19
- @tree = Tree.new self.hashify
20
- end
1
+ $:.unshift File.dirname(__FILE__)
21
2
 
22
- def compile
23
- #
24
- # Parse the variables and mixins
25
- #
26
- # We use symbolic keys, such as :mixins, to store LESS-only data,
27
- # each branch has its own :mixins => [], and :variables => {}
28
- # Once a declaration has been recognised as LESS-specific, it is copied
29
- # in the appropriate data structure in that branch. The declaration itself
30
- # can then be deleted.
31
- #
32
- @tree = @tree.traverse :leaf do |key, value, path, node|
33
- matched = if match = key.match( REGEX[:variable] )
34
- node[:variables] ||= Tree.new
35
- node[:variables][ match.captures.first ] = value
36
- elsif value == :mixin
37
- node[:mixins] ||= []
38
- node[:mixins] << key
39
- end
40
- node.delete key if matched # Delete the property if it's LESS-specific
41
- end
3
+ require 'engine/builder'
4
+ require 'engine/nodes'
42
5
 
43
- #
44
- # Evaluate mixins
45
- #
46
- @tree = @tree.traverse :branch do |path, node|
47
- if node.include? :mixins
48
- node[:mixins].each do |m|
49
- @tree.find( :mixin, m.delete(' ').split('>') ).each {|k, v| node[ k ] = v }
50
- end
51
- end
52
- end
53
-
54
- # Call `evaluate` on variables, such as '@dark: @light / 2'
55
- @tree = @tree.traverse :branch do |path, node|
56
- node.vars.each do |key, value|
57
- evaluate key, value, path, node.vars
58
- end if node.vars?
59
- end
60
-
61
- # Call `evaluate` on css properties, such as 'font-size: @big'
62
- @tree = @tree.traverse :leaf do |key, value, path, node|
63
- evaluate key, value, path, node
64
- end
65
-
66
- #
67
- # Evaluate operations (2+2)
68
- #
69
- # Units are: 1px, 1em, 1%, #111
70
- @tree = @tree.traverse :leaf do |key, value, path, node|
71
- node[ key ] = value.gsub /(#{REGEX[:operand]}(\s?)[-+\/*](\4))+(#{REGEX[:operand]})/ do |operation|
72
- # Disallow operations on certain compound declarations
73
- if COMPOUND[ key ]
74
- next value
75
- else
76
- raise CompoundOperationError, "#{key}: #{value}"
77
- end if COMPOUND.include? key
6
+ begin
7
+ require 'engine/parser'
8
+ rescue LoadError
9
+ Treetop.load LESS_GRAMMAR
10
+ end
78
11
 
79
- if (unit = operation.scan(/#{REGEX[:numeric]}|(#)/i).flatten.compact.uniq).size <= 1
80
- unit = unit.join
81
- operation = if unit == '#'
82
- evaluate = lambda do |v|
83
- result = eval v
84
- unit + ( result.zero?? '000' : result.to_s(16) )
85
- end
86
- operation.gsub REGEX[:color] do
87
- hex = $1 * ( $1.size < 6 ? 6 / $1.size : 1 )
88
- hex.to_i(16)
89
- end.delete unit
90
- else
91
- evaluate = lambda {|v| eval( v ).to_s + unit }
92
- operation.gsub REGEX[:unit], ''
93
- end.to_s
94
- next if operation.match /[a-z]/i
95
- evaluate.call operation
96
- else
97
- raise MixedUnitsError, value
98
- end
99
- end
12
+ module Less
13
+ class Engine
14
+ attr_reader :css, :less
15
+
16
+ def initialize obj
17
+ @less = if obj.is_a? File
18
+ @path = File.dirname(File.expand_path obj.path)
19
+ obj.read
20
+ elsif obj.is_a? String
21
+ obj.dup
22
+ else
23
+ raise ArgumentError, "argument must be an instance of File or String!"
100
24
  end
25
+
26
+ @parser = LessParser.new
101
27
  end
102
- alias render compile
103
28
 
104
- #
105
- # Evaluate variables
106
- #
107
- def evaluate key, expression, path, node
108
- if expression.is_a? String and expression.include? '@' # There's a var to evaluate
109
- expression.scan /#{REGEX[:path]}#{REGEX[:variable]}/ do |var|
110
- name = var.last
111
- var = var.join.delete ' '
112
-
113
- value = if var.include? '>'
114
- @tree.find :var, var.split('>') # Try finding it in a specific namespace
115
- else
116
- node.var(var) or @tree.nearest var, path # Try local first, then nearest scope
117
- end
118
-
119
- if value
120
- # Substitute variable with value
121
- node[ key ] = node[ key ].gsub /#{REGEX[:path]}@#{name}/, value
122
- else
123
- node.delete key # Discard the declaration if the variable wasn't found
124
- end
125
- end
29
+ def parse env = Node::Element.new
30
+ root = @parser.parse(self.prepare)
31
+
32
+ if root
33
+ @tree = root.build env.tap {|e| e.file = @path }
34
+ else
35
+ raise SyntaxError, @parser.failure_message
126
36
  end
37
+
38
+ @tree
127
39
  end
40
+ alias :to_tree :parse
128
41
 
129
- def to_css chain = :desc
130
- self.compile.to_css chain
42
+ def to_css
43
+ @css || @css = self.parse.to_css
131
44
  end
132
45
 
133
- def hashify
134
- #
135
- # Parse the LESS structure into a hash
136
- #
137
- ###
138
- # less: color: black;
139
- # hashify: "color" => "black"
140
- #
141
- hash = self.gsub(/\r\n/, "\n"). # m$
142
- gsub(/"/, "'"). # " to '
143
- gsub(/'(.*?)'/) { "'" + CGI.escape( $1 ) + "'" }. # Escape string values
144
- gsub(/\/\/.*\n/, ''). # Comments //
145
- gsub(/\/\*.*?\*\//m, ''). # Comments /*
146
- gsub(/\s+/, ' '). # Replace \t\n
147
- gsub(/(#{REGEX[:property]}): *([^\{\}]+?) *(;|(?=\}))/,'"\1"=>"\2",'). # Rules
148
- gsub(/\}/, "},"). # Closing }
149
- gsub(/([, ]*)(#{REGEX[:selector]}+?)[ \n]*(?=\{)/, '\1"\2"=>'). # Selectors
150
- gsub(/([.#][->\w .#]+);/, '"\\1" => :mixin,') # Mixins
151
- eval "{#{ hash }}" # Return {hash}
46
+ def prepare
47
+ @less.gsub(/\r\n/, "\n").gsub(/\t/, ' ')
152
48
  end
153
49
  end
154
50
  end
@@ -0,0 +1,8 @@
1
+ module Builder
2
+ def build env = Less::Element.new
3
+ elements.map do |e|
4
+ e.build env if e.respond_to? :build
5
+ end
6
+ env
7
+ end
8
+ end
@@ -0,0 +1,374 @@
1
+ grammar Less
2
+ rule primary
3
+ (declaration / ruleset / import / comment)+ <Builder> / declaration* <Builder> / import* <Builder> / comment*
4
+ end
5
+
6
+ rule comment
7
+ ws '/*' (!'*/' . )* '*/' ws / ws '//' (!"\n" .)* "\n" ws
8
+ end
9
+
10
+ #
11
+ # div, .class, body > p {...}
12
+ #
13
+ rule ruleset
14
+ selectors "{" ws primary ws "}" ws {
15
+ def build env
16
+ # Build the ruleset for each selector
17
+ selectors.build(env, :tree).each do |sel|
18
+ primary.build sel
19
+ end
20
+ end
21
+ } / ws selectors ';' ws {
22
+ def build env
23
+ selectors.build(env, :path).each do |path|
24
+ rules = path.inject(env.root) do |current, node|
25
+ current.descend(node.selector, node) or raise MixinNameError, path.join
26
+ end.rules
27
+ env.rules += rules
28
+ end
29
+ end
30
+ }
31
+ end
32
+
33
+ rule import
34
+ "@import" S url:(string / url) medias? s ';' ws {
35
+ def build env
36
+ path = File.join(env.root.file, url.value)
37
+ path += '.less' unless path =~ /\.less$/
38
+ if File.exist? path
39
+ imported = Less::Engine.new(File.new path).to_tree
40
+ env.rules += imported.rules
41
+ else
42
+ raise ImportError, path
43
+ end
44
+ end
45
+ }
46
+ end
47
+
48
+ rule url
49
+ 'url(' path:(string / [-a-zA-Z0-9_%$/.&=:;#+?]+) ')' {
50
+ def build env = nil
51
+ Node::String.new(CGI.unescape path.text_value)
52
+ end
53
+
54
+ def value
55
+ build
56
+ end
57
+ }
58
+ end
59
+
60
+ rule medias
61
+ [-a-z]+ (s ',' s [a-z]+)*
62
+ end
63
+
64
+ rule selectors
65
+ ws selector tail:(s ',' ws selector)* ws {
66
+ def build env, method
67
+ all.map do |e|
68
+ e.send(method, env) if e.respond_to? method
69
+ end.compact
70
+ end
71
+
72
+ def all
73
+ [selector] + tail.elements.map {|e| e.selector }
74
+ end
75
+ }
76
+ end
77
+
78
+ #
79
+ # div > p a {...}
80
+ #
81
+ rule selector
82
+ (s select element s)+ {
83
+ def tree env
84
+ elements.inject(env) do |node, e|
85
+ node << Node::Element.new(e.element.text_value, e.select.text_value)
86
+ node.last
87
+ end
88
+ end
89
+
90
+ def path env
91
+ elements.map do |e|
92
+ Node::Element.new(e.element.text_value, e.select.text_value)
93
+ end
94
+ end
95
+ }
96
+ end
97
+
98
+ #
99
+ # @my-var: 12px;
100
+ # height: 100%;
101
+ #
102
+ rule declaration
103
+ ws name:(ident / variable) s ':' s expression s (';'/ ws &'}') ws {
104
+ def build env
105
+ env << (name.text_value =~ /^@/ ? Node::Variable : Node::Property).new(name.text_value)
106
+ expression.build env
107
+ end
108
+ # Empty rule
109
+ } / ws ident s ':' s ';' ws
110
+ end
111
+
112
+ #
113
+ # An operation or compound value
114
+ #
115
+ rule expression
116
+ entity (operator / S) expression <Builder> / entity
117
+ end
118
+
119
+ #
120
+ # Entity: Any whitespace delimited token
121
+ #
122
+ rule entity
123
+ function / fonts / keyword / accessor / variable / literal / important
124
+ end
125
+
126
+ rule fonts
127
+ font family:(s ',' s font)+ {
128
+ def build env
129
+ fonts = ([font] + family.elements.map {|f| f.font }).map do |font|
130
+ font.build env
131
+ end
132
+ env.identifiers.last << Node::FontFamily.new(fonts)
133
+ end
134
+ }
135
+ end
136
+
137
+ rule font
138
+ [a-zA-Z] [-a-zA-Z0-9]* {
139
+ def build env
140
+ Node::Keyword.new(text_value)
141
+ end
142
+ } / string {
143
+ def build env
144
+ Node::String.new(text_value)
145
+ end
146
+ }
147
+ end
148
+
149
+ #
150
+ # An identifier
151
+ #
152
+ rule ident
153
+ '-'? [-a-z0-9_]+
154
+ end
155
+
156
+ rule variable
157
+ '@' [-a-zA-Z0-9_]+ {
158
+ def build env
159
+ env.identifiers.last << env.nearest(text_value)
160
+ end
161
+ }
162
+ end
163
+
164
+ #
165
+ # div / .class / #id / input[type="text"] / lang(fr)
166
+ #
167
+ rule element
168
+ (class_id / tag / ident) attribute* ('(' ident ')')? / '@media' / '@font-face'
169
+ end
170
+
171
+ rule class_id
172
+ tag? class+ / tag? id
173
+ end
174
+
175
+ #
176
+ # [type="text"]
177
+ #
178
+ rule attribute
179
+ '[' [a-z]+ ([|~]? '=')? (tag / string) ']'
180
+ end
181
+
182
+ rule class
183
+ '.' [_a-z] [-a-zA-Z0-9_]*
184
+ end
185
+
186
+ rule id
187
+ '#' [_a-z] [-a-zA-Z0-9_]*
188
+ end
189
+
190
+ rule tag
191
+ [a-zA-Z] [-a-zA-Z]* [0-9]? / '*'
192
+ end
193
+
194
+ rule select
195
+ (s [:+>] s / S)?
196
+ end
197
+
198
+ # TODO: Merge this with attribute rule
199
+ rule accessor
200
+ ident:(class_id / tag) '[' attr:(string / variable) ']' {
201
+ def build env
202
+ env.identifiers.last << env.nearest(ident.text_value)[attr.text_value.delete(%q["'])].evaluate
203
+ end
204
+ }
205
+ end
206
+
207
+ rule operator
208
+ S [-+*/] S {
209
+ def build env
210
+ env.identifiers.last << Node::Operator.new(text_value.strip)
211
+ end
212
+ } / [-+*/] {
213
+ def build env
214
+ env.identifiers.last << Node::Operator.new(text_value)
215
+ end
216
+ }
217
+ end
218
+
219
+ #
220
+ # Tokens which don't need to be evaluated
221
+ #
222
+ rule literal
223
+ color / (dimension / [-a-z]+) '/' dimension {
224
+ def build env
225
+ env.identifiers.last << Node::Anonymous.new(text_value)
226
+ end
227
+ } / number unit {
228
+ def build env
229
+ env.identifiers.last << Node::Number.new(number.text_value, unit.text_value)
230
+ end
231
+ } / string {
232
+ def build env
233
+ env.identifiers.last << Node::String.new(text_value)
234
+ end
235
+ }
236
+ end
237
+
238
+ # !important
239
+ rule important
240
+ '!important' {
241
+ def build env
242
+ env.identifiers.last << Node::Keyword.new(text_value)
243
+ end
244
+ }
245
+ end
246
+
247
+ #
248
+ # `blue`, `small`, `normal` etc.
249
+ #
250
+ rule keyword
251
+ [a-zA-Z] [-a-zA-Z]* !ns {
252
+ def build env
253
+ env.identifiers.last << Node::Keyword.new(text_value)
254
+ end
255
+ }
256
+ end
257
+
258
+ #
259
+ # 'hello world' / "hello world"
260
+ #
261
+ rule string
262
+ "'" content:(!"'" . )* "'" {
263
+ def value
264
+ text_value[1...-1]
265
+ end
266
+ } / ["] content:(!["] . )* ["] {
267
+ def value
268
+ text_value[1...-1]
269
+ end
270
+ }
271
+ end
272
+
273
+ #
274
+ # Numbers & Units
275
+ #
276
+ rule dimension
277
+ number unit
278
+ end
279
+
280
+ rule number
281
+ '-'? [0-9]* '.' [0-9]+ / '-'? [0-9]+
282
+ end
283
+
284
+ rule unit
285
+ ('px'/'em'/'pc'/'%'/'pt'/'cm'/'mm')?
286
+ end
287
+
288
+
289
+ #
290
+ # Color
291
+ #
292
+ rule color
293
+ '#' hex {
294
+ def build env
295
+ env.identifiers.last << Node::Color.new(hex.text_value)
296
+ end
297
+ } / fn:(('hsl'/'rgb') 'a'?) '(' arguments ')' {
298
+ def build env
299
+ args = arguments.build env
300
+ env.identifiers.last << Node::Function.new(fn.text_value, args.flatten)
301
+ end
302
+ }
303
+ end
304
+
305
+ rule hex
306
+ [a-fA-F0-9] [a-fA-F0-9] [a-fA-F0-9]+
307
+ end
308
+
309
+ #
310
+ # Functions and arguments
311
+ #
312
+ rule function
313
+ name:([-a-zA-Z_]+) '(' arguments ')' {
314
+ def build env
315
+ args = arguments.build env
316
+ env.identifiers.last << Node::Function.new(name.text_value, [args].flatten)
317
+ end
318
+ }
319
+ end
320
+
321
+ rule arguments
322
+ argument s ',' s arguments {
323
+ def build env
324
+ elements.map do |e|
325
+ e.build env if e.respond_to? :build
326
+ end.compact
327
+ end
328
+ } / argument
329
+ end
330
+
331
+ rule argument
332
+ color {
333
+ def build env
334
+ Node::Color.new text_value
335
+ end
336
+ } / number unit {
337
+ def build env
338
+ Node::Number.new number.text_value, unit.text_value
339
+ end
340
+ } / string {
341
+ def build env
342
+ Node::String.new text_value
343
+ end
344
+ } / [a-zA-Z]+ '=' dimension {
345
+ def build env
346
+ Node::Anonymous.new text_value
347
+ end
348
+ } / [-a-zA-Z0-9_%$/.&=:;#+?]+ {
349
+ def build env
350
+ Node::String.new text_value
351
+ end
352
+ }
353
+ end
354
+
355
+ #
356
+ # Whitespace
357
+ #
358
+ rule s
359
+ [ ]*
360
+ end
361
+
362
+ rule S
363
+ [ ]+
364
+ end
365
+
366
+ rule ws
367
+ [\n ]*
368
+ end
369
+
370
+ # Non-space char
371
+ rule ns
372
+ ![ ;] .
373
+ end
374
+ end