unboxed-less 1.2.12

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 (75) hide show
  1. data/.gitignore +4 -0
  2. data/CHANGELOG +62 -0
  3. data/LICENSE +179 -0
  4. data/README.md +48 -0
  5. data/Rakefile +60 -0
  6. data/VERSION +1 -0
  7. data/bin/lessc +92 -0
  8. data/lib/ext.rb +31 -0
  9. data/lib/less.rb +32 -0
  10. data/lib/less/command.rb +98 -0
  11. data/lib/less/engine.rb +55 -0
  12. data/lib/less/engine/grammar/common.tt +29 -0
  13. data/lib/less/engine/grammar/entity.tt +130 -0
  14. data/lib/less/engine/grammar/less.tt +326 -0
  15. data/lib/less/engine/nodes.rb +9 -0
  16. data/lib/less/engine/nodes/element.rb +278 -0
  17. data/lib/less/engine/nodes/entity.rb +79 -0
  18. data/lib/less/engine/nodes/function.rb +84 -0
  19. data/lib/less/engine/nodes/literal.rb +171 -0
  20. data/lib/less/engine/nodes/property.rb +231 -0
  21. data/lib/less/engine/nodes/ruleset.rb +12 -0
  22. data/lib/less/engine/nodes/selector.rb +44 -0
  23. data/spec/command_spec.rb +102 -0
  24. data/spec/css/accessors.css +18 -0
  25. data/spec/css/big.css +3768 -0
  26. data/spec/css/colors.css +14 -0
  27. data/spec/css/comments.css +9 -0
  28. data/spec/css/css-3.css +17 -0
  29. data/spec/css/css.css +50 -0
  30. data/spec/css/functions.css +6 -0
  31. data/spec/css/import.css +12 -0
  32. data/spec/css/lazy-eval.css +1 -0
  33. data/spec/css/mixins-args.css +32 -0
  34. data/spec/css/mixins.css +28 -0
  35. data/spec/css/operations.css +28 -0
  36. data/spec/css/parens.css +20 -0
  37. data/spec/css/rulesets.css +17 -0
  38. data/spec/css/scope.css +11 -0
  39. data/spec/css/selectors.css +13 -0
  40. data/spec/css/strings.css +12 -0
  41. data/spec/css/variables.css +7 -0
  42. data/spec/css/whitespace.css +7 -0
  43. data/spec/engine_spec.rb +112 -0
  44. data/spec/less/accessors.less +20 -0
  45. data/spec/less/big.less +4810 -0
  46. data/spec/less/colors.less +35 -0
  47. data/spec/less/comments.less +46 -0
  48. data/spec/less/css-3.less +45 -0
  49. data/spec/less/css.less +104 -0
  50. data/spec/less/exceptions/mixed-units-error.less +3 -0
  51. data/spec/less/exceptions/name-error-1.0.less +3 -0
  52. data/spec/less/exceptions/syntax-error-1.0.less +3 -0
  53. data/spec/less/functions.less +6 -0
  54. data/spec/less/hidden.less +25 -0
  55. data/spec/less/import.less +8 -0
  56. data/spec/less/import/import-test-a.less +2 -0
  57. data/spec/less/import/import-test-b.less +8 -0
  58. data/spec/less/import/import-test-c.less +7 -0
  59. data/spec/less/import/import-test-d.css +1 -0
  60. data/spec/less/lazy-eval.less +6 -0
  61. data/spec/less/literal-css.less +11 -0
  62. data/spec/less/mixins-args.less +59 -0
  63. data/spec/less/mixins.less +43 -0
  64. data/spec/less/operations.less +39 -0
  65. data/spec/less/parens.less +26 -0
  66. data/spec/less/rulesets.less +30 -0
  67. data/spec/less/scope.less +32 -0
  68. data/spec/less/selectors.less +24 -0
  69. data/spec/less/strings.less +14 -0
  70. data/spec/less/variables.less +24 -0
  71. data/spec/less/whitespace.less +34 -0
  72. data/spec/spec.css +50 -0
  73. data/spec/spec_helper.rb +8 -0
  74. data/unboxed-less.gemspec +121 -0
  75. metadata +140 -0
@@ -0,0 +1,31 @@
1
+ module Treetop
2
+ module Runtime
3
+ class CompiledParser
4
+ def failure_message color
5
+ o = color ? Mutter.new.clear : lambda {|i, *args| i }
6
+ return nil unless (tf = terminal_failures) && tf.size > 0
7
+ msg = "on line #{failure_line}: expected " + (
8
+ tf.size == 1 ?
9
+ o[tf[0].expected_string, :yellow] :
10
+ "one of #{o[tf.map {|f| f.expected_string }.uniq * ' ', :yellow]}"
11
+ )
12
+ f = input[failure_index]
13
+ got = case f
14
+ when "\n" then o['\n', :cyan]
15
+ when nil then o["EOF", :cyan]
16
+ when ' ' then o["white-space", :cyan]
17
+ else o[f.chr, :yellow]
18
+ end
19
+ msg += " got #{got} after:\n\n#{input[index...failure_index]}\n"
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ unless :symbol.respond_to?(:to_proc)
26
+ class Symbol
27
+ def to_proc
28
+ Proc.new {|*args| args.shift.__send__(self, *args) }
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'cgi'
4
+ require 'treetop'
5
+ require 'delegate'
6
+
7
+ LESS_ROOT = File.expand_path(File.dirname(__FILE__))
8
+ LESS_PARSER = File.join(LESS_ROOT, 'less', 'engine', 'parser.rb')
9
+ LESS_GRAMMAR = File.join(LESS_ROOT, 'less', 'engine', 'grammar')
10
+
11
+ require 'ext'
12
+ require 'less/command'
13
+ require 'less/engine'
14
+
15
+ module Less
16
+ MixedUnitsError = Class.new(RuntimeError)
17
+ PathError = Class.new(RuntimeError)
18
+ VariableNameError = Class.new(NameError)
19
+ MixinNameError = Class.new(NameError)
20
+ SyntaxError = Class.new(RuntimeError)
21
+ ImportError = Class.new(RuntimeError)
22
+
23
+ $verbose = false
24
+
25
+ def self.version
26
+ File.read( File.join( File.dirname(__FILE__), '..', 'VERSION') ).strip
27
+ end
28
+
29
+ def self.parse less
30
+ Engine.new(less).to_css
31
+ end
32
+ end
@@ -0,0 +1,98 @@
1
+ module Less
2
+ class Command
3
+ attr_accessor :source, :destination, :options
4
+
5
+ def initialize options
6
+ $verbose = options[:debug]
7
+ @source = options[:source]
8
+ @destination = (options[:destination] || options[:source]).gsub /\.(less|lss)/, '.css'
9
+ @options = options
10
+ end
11
+
12
+ def watch?() @options[:watch] end
13
+ def compress?() @options[:compress] end
14
+ def debug?() @options[:debug] end
15
+
16
+ # little function which allows us to
17
+ # Ctrl-C exit inside the passed block
18
+ def watch
19
+ begin
20
+ yield
21
+ rescue Interrupt
22
+ puts
23
+ exit 0
24
+ end
25
+ end
26
+
27
+ def run!
28
+ if watch?
29
+ parse(true) unless File.exist? @destination
30
+
31
+ log "Watching for changes in #@source... Ctrl-C to abort.\n: "
32
+
33
+ # Main watch loop
34
+ loop do
35
+ watch { sleep 1 }
36
+
37
+ # File has changed
38
+ if File.stat( @source ).mtime > File.stat( @destination ).mtime
39
+ print "Change detected... "
40
+
41
+ # Loop until error is fixed
42
+ until parse
43
+ log "Press [return] to continue..."
44
+ watch { $stdin.gets }
45
+ end
46
+ end
47
+ end
48
+ else
49
+ parse
50
+ end
51
+ end
52
+
53
+ def parse is_new = false
54
+ begin
55
+ # Create a new Less object with the contents of a file
56
+ css = Less::Engine.new(File.new(@source), @options).to_css
57
+ css = css.delete " \n" if compress?
58
+
59
+ File.open( @destination, "w" ) do |file|
60
+ file.write css
61
+ end
62
+ print "* #{is_new ? 'Created' : 'Updated'} " +
63
+ "#{@destination.split('/').last}\n: " if watch?
64
+ rescue Errno::ENOENT => e
65
+ abort "#{e}"
66
+ rescue SyntaxError => e
67
+ err "#{e}\n", "Syntax"
68
+ rescue MixedUnitsError => e
69
+ err "`#{e}` you're mixing units together! What do you expect?\n", "Mixed Units"
70
+ rescue PathError => e
71
+ err "`#{e}` was not found.\n", "Path"
72
+ rescue VariableNameError => e
73
+ err "#{e} is undefined.\n", "Variable Name"
74
+ rescue MixinNameError => e
75
+ err "#{e} is undefined.\n", "Mixin Name"
76
+ else
77
+ true
78
+ end
79
+ end
80
+
81
+ # Just a logging function to avoid typing '*'
82
+ def log s = ''
83
+ print '* ' + s.to_s
84
+ end
85
+
86
+ def err s = '', type = ''
87
+ type = type.strip + ' ' unless type.empty?
88
+ $stderr.print "! #{type}Error: #{s}"
89
+ if @options[:growl]
90
+ growl = Growl.new
91
+ growl.title = "LESS"
92
+ growl.message = "#{type}Error in #@source!"
93
+ growl.run
94
+ false
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,55 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'engine/nodes'
4
+
5
+ begin
6
+ require 'engine/parser'
7
+ rescue LoadError
8
+ Treetop.load File.join(LESS_GRAMMAR, 'common.tt')
9
+ Treetop.load File.join(LESS_GRAMMAR, 'entity.tt')
10
+ Treetop.load File.join(LESS_GRAMMAR, 'less.tt')
11
+ end
12
+
13
+ module Less
14
+ class Engine
15
+ attr_reader :css, :less
16
+
17
+ def initialize obj, options = {}
18
+ @less = if obj.is_a? File
19
+ @path = File.dirname File.expand_path(obj.path)
20
+ obj.read
21
+ elsif obj.is_a? String
22
+ obj.dup
23
+ else
24
+ raise ArgumentError, "argument must be an instance of File or String!"
25
+ end
26
+
27
+ @options = options
28
+ @parser = StyleSheetParser.new
29
+ end
30
+
31
+ def parse build = true, env = Node::Element.new
32
+ root = @parser.parse(self.prepare)
33
+
34
+ return root unless build
35
+
36
+ if root
37
+ env.file = @path
38
+ @tree = root.build env
39
+ else
40
+ raise SyntaxError, @parser.failure_message(@options[:color])
41
+ end
42
+
43
+ @tree
44
+ end
45
+ alias :to_tree :parse
46
+
47
+ def to_css
48
+ @css || @css = self.parse.group.to_css
49
+ end
50
+
51
+ def prepare
52
+ @less.gsub(/\r\n/, "\n").gsub(/\t/, ' ')
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,29 @@
1
+ module Less
2
+ module StyleSheet
3
+ grammar Common
4
+ #
5
+ # Whitespace
6
+ #
7
+ rule s
8
+ [ ]*
9
+ end
10
+
11
+ rule S
12
+ [ ]+
13
+ end
14
+
15
+ rule ws
16
+ [\n ]*
17
+ end
18
+
19
+ rule WS
20
+ [\n ]+
21
+ end
22
+
23
+ # Non-space char
24
+ rule ns
25
+ ![ ;,!})\n] .
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,130 @@
1
+ module Less
2
+ module StyleSheet
3
+ grammar Entity
4
+ #
5
+ # Entity: Any whitespace delimited token
6
+ #
7
+ rule entity
8
+ url / function / accessor / keyword / variable / literal / font
9
+ end
10
+
11
+ rule fonts
12
+ font family:(s ',' s font)+ {
13
+ def build
14
+ Node::FontFamily.new(all.map(&:build))
15
+ end
16
+
17
+ def all
18
+ [font] + family.elements.map {|f| f.font }
19
+ end
20
+ }
21
+ end
22
+
23
+ rule font
24
+ [a-zA-Z] [-a-zA-Z0-9]* !ns {
25
+ def build
26
+ Node::Keyword.new(text_value)
27
+ end
28
+ } / string {
29
+ def build
30
+ Node::Quoted.new(text_value)
31
+ end
32
+ }
33
+ end
34
+
35
+ #
36
+ # Tokens which don't need to be evaluated
37
+ #
38
+ rule literal
39
+ color / (dimension / [-a-z]+) '/' dimension {
40
+ def build
41
+ Node::Anonymous.new(text_value)
42
+ end
43
+ } / number unit {
44
+ def build
45
+ Node::Number.new(number.text_value, unit.text_value)
46
+ end
47
+ } / string {
48
+ def build
49
+ Node::Quoted.new(text_value)
50
+ end
51
+ }
52
+ end
53
+
54
+ #
55
+ # `blue`, `small`, `normal` etc.
56
+ #
57
+ rule keyword
58
+ [-a-zA-Z]+ !ns {
59
+ def build
60
+ Node::Keyword.new(text_value)
61
+ end
62
+ }
63
+ end
64
+
65
+ #
66
+ # 'hello world' / "hello world"
67
+ #
68
+ rule string
69
+ "'" content:(!"'" . )* "'" {
70
+ def value
71
+ content.text_value
72
+ end
73
+ } / ["] content:(!["] . )* ["] {
74
+ def value
75
+ content.text_value
76
+ end
77
+ }
78
+ end
79
+
80
+ #
81
+ # Numbers & Units
82
+ #
83
+ rule dimension
84
+ number unit
85
+ end
86
+
87
+ rule number
88
+ '-'? [0-9]* '.' [0-9]+ / '-'? [0-9]+
89
+ end
90
+
91
+ rule unit
92
+ ('px'/'em'/'pc'/'%'/'ex'/'in'/'deg'/'s'/'pt'/'cm'/'mm')?
93
+ end
94
+
95
+ #
96
+ # Color
97
+ #
98
+ rule color
99
+ '#' rgb {
100
+ def build
101
+ Node::Color.new(*rgb.build)
102
+ end
103
+ } / fn:(('hsl'/'rgb') 'a'?) arguments {
104
+ def build
105
+ Node::Function.new(fn.text_value, arguments.build.flatten)
106
+ end
107
+ }
108
+ end
109
+
110
+ #
111
+ # 00ffdd / 0fd
112
+ #
113
+ rule rgb
114
+ r:(hex hex) g:(hex hex) b:(hex hex) {
115
+ def build
116
+ [r.text_value, g.text_value, b.text_value]
117
+ end
118
+ } / r:hex g:hex b:hex {
119
+ def build
120
+ [r.text_value, g.text_value, b.text_value].map {|c| c * 2 }
121
+ end
122
+ }
123
+ end
124
+
125
+ rule hex
126
+ [a-fA-F0-9]
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,326 @@
1
+ module Less
2
+ grammar StyleSheet
3
+ include Common
4
+ include Entity
5
+
6
+ rule primary
7
+ (import / declaration / mixin / ruleset / comment)* {
8
+ def build env = Less::Element.new
9
+ elements.map do |e|
10
+ e.build env if e.respond_to? :build
11
+ end; env
12
+ end
13
+ }
14
+ end
15
+
16
+ rule comment
17
+ ws '/*' (!'*/' . )* '*/' ws / ws '//' (!"\n" .)* "\n" ws
18
+ end
19
+
20
+ #
21
+ # div, .class, body > p {...}
22
+ #
23
+ rule ruleset
24
+ selectors "{" ws primary ws "}" s hide:(';'?) ws {
25
+ def build env
26
+ # Build the ruleset for each selector
27
+ selectors.build(env, :ruleset).each do |sel|
28
+ sel.hide unless hide.empty?
29
+ primary.build sel
30
+ end
31
+ end
32
+ # Mixin Declaration
33
+ } / '.' name:[-a-zA-Z0-9_]+ ws parameters ws "{" ws primary ws "}" ws {
34
+ def build env
35
+ env << Node::Mixin::Def.new(name.text_value, parameters.build(env))
36
+ primary.build env.last
37
+ #env.last
38
+ end
39
+ }
40
+ end
41
+
42
+ rule mixin
43
+ name:('.' [-a-zA-Z0-9_]+) args:(arguments) s ';' ws {
44
+ def build env
45
+ definition = env.nearest(name.text_value, :mixin) or raise MixinNameError, name.text_value
46
+ params = args.build.map {|i| Node::Expression.new i } unless args.empty?
47
+ env << Node::Mixin::Call.new(definition, params || [], env)
48
+ end
49
+ } / ws selectors ';' ws {
50
+ def build env
51
+ selectors.build(env, :mixin).each do |path|
52
+ rules = path.inject(env.root) do |current, node|
53
+ current.descend(node.selector, node) or raise MixinNameError, selectors.text_value
54
+ end.rules
55
+ env.rules += rules
56
+ #env.mix(rules)
57
+ end
58
+ end
59
+ }
60
+ end
61
+
62
+ rule selectors
63
+ ws selector tail:(s ',' ws selector)* ws {
64
+ def build env, method
65
+ all.map do |e|
66
+ e.send(method, env) if e.respond_to? method
67
+ end.compact
68
+ end
69
+
70
+ def all
71
+ [selector] + tail.elements.map {|e| e.selector }
72
+ end
73
+ }
74
+ end
75
+
76
+ #
77
+ # div > p a {...}
78
+ #
79
+ rule selector
80
+ sel:(s select element s)+ '' {
81
+ def ruleset env
82
+ sel.elements.inject(env) do |node, e|
83
+ node << Node::Element.new(e.element.text_value, e.select.text_value)
84
+ node.last
85
+ end
86
+ end
87
+
88
+ def mixin env
89
+ sel.elements.map do |e|
90
+ Node::Element.new(e.element.text_value, e.select.text_value)
91
+ end
92
+ end
93
+ }
94
+ end
95
+
96
+ rule parameters
97
+ '(' s ')' {
98
+ def build env
99
+ []
100
+ end
101
+ } / '(' parameter tail:(s ',' s parameter)* ')' {
102
+ def build env
103
+ all.map do |e|
104
+ e.build(env)
105
+ end
106
+ end
107
+
108
+ def all
109
+ [parameter] + tail.elements.map {|e| e.parameter }
110
+ end
111
+ }
112
+ end
113
+
114
+ rule parameter
115
+ variable s ':' s expressions {
116
+ def build env
117
+ Node::Variable.new(variable.text_value, expressions.build(env), env)
118
+ end
119
+ }
120
+ end
121
+
122
+ rule import
123
+ ws "@import" S url:(string / url) medias? s ';' ws {
124
+ def build env
125
+ path = File.join(env.root.file || Dir.pwd, url.value)
126
+ path += '.less' unless path =~ /\.(le|c)ss$/
127
+ if File.exist? path
128
+ unless env.root.imported.include?(path)
129
+ env.root.imported << path
130
+ env.rules += Less::Engine.new(File.new(path)).to_tree.rules
131
+ end
132
+ else
133
+ raise ImportError, path
134
+ end
135
+ end
136
+ }
137
+ end
138
+
139
+ rule url
140
+ 'url(' path:(string / [-a-zA-Z0-9_%$/.&=:;#+?]+) ')' {
141
+ def build env = nil
142
+ Node::Function.new('url', value)
143
+ end
144
+
145
+ def value
146
+ Node::Quoted.new CGI.unescape(path.text_value)
147
+ end
148
+ }
149
+ end
150
+
151
+ rule medias
152
+ [-a-z]+ (s ',' s [a-z]+)*
153
+ end
154
+
155
+ #
156
+ # @my-var: 12px;
157
+ # height: 100%;
158
+ #
159
+ rule declaration
160
+ ws name:(ident / variable) s ':' s expressions tail:(ws ',' ws expressions)* s (';'/ ws &'}') ws {
161
+ def build env
162
+ result = all.map {|e| e.build(env) if e.respond_to? :build }.compact
163
+ env << (name.text_value =~ /^@/ ?
164
+ Node::Variable : Node::Property).new(name.text_value, result, env)
165
+ end
166
+
167
+ def all
168
+ [expressions] + tail.elements.map {|f| f.expressions }
169
+ end
170
+ # Empty rule
171
+ } / ws ident s ':' s ';' ws
172
+ end
173
+
174
+ #
175
+ # An operation or compound value
176
+ #
177
+ rule expressions
178
+ # Operation
179
+ expression tail:(operator expression)+ {
180
+ def build env = nil
181
+ ret = all.map {|e| e.build(env) }.flatten.compact
182
+ ret.size == 1 ? ret.first : ret
183
+ end
184
+
185
+ def all
186
+ [expression] + tail.elements.map {|i| [i.operator, i.expression] }.flatten.compact
187
+ end
188
+ # Space-delimited expressions
189
+ } / expression tail:(WS expression)* i:important? {
190
+ def build env = nil
191
+ all.map {|e| e.build(env) if e.respond_to? :build }.compact
192
+ end
193
+
194
+ def all
195
+ [expression] + tail.elements.map {|f| f.expression } + [i]
196
+ end
197
+ # Catch-all rule
198
+ } / [-a-zA-Z0-9_%*/.&=:,#+? \[\]()]+ {
199
+ def build env
200
+ [Node::Anonymous.new(text_value)]
201
+ end
202
+ }
203
+ end
204
+
205
+ rule expression
206
+ '(' s expressions s ')' {
207
+ def build env = nil
208
+ Node::Expression.new(['('] + expressions.build(env).flatten + [')'])
209
+ end
210
+ } / entity '' {
211
+ def build env = nil
212
+ e = entity.method(:build).arity.zero?? entity.build : entity.build(env)
213
+ if e.is_a?( Array )
214
+ e = e.flatten.compact
215
+ e = e.size == 1 ? e.first : e
216
+ end
217
+ e
218
+ end
219
+ }
220
+ end
221
+
222
+ # !important
223
+ rule important
224
+ s '!' s 'important' {
225
+ def build env = nil
226
+ Node::Keyword.new(text_value.strip)
227
+ end
228
+ }
229
+ end
230
+
231
+ #
232
+ # An identifier
233
+ #
234
+ rule ident
235
+ '*'? '-'? [-a-z_] [-a-z0-9_]*
236
+ end
237
+
238
+ rule variable
239
+ '@' [-a-zA-Z0-9_]+ {
240
+ def build
241
+ Node::Variable.new(text_value)
242
+ end
243
+ }
244
+ end
245
+
246
+ #
247
+ # div / .class / #id / input[type="text"] / lang(fr)
248
+ #
249
+ rule element
250
+ ((class / id / tag / ident) attribute* ('(' (selector / number) ')')?)+ / attribute+ / '@media' / '@font-face'
251
+ end
252
+
253
+ #
254
+ # [type="text"]
255
+ #
256
+ rule attribute
257
+ '[' tag ([|~*$^]? '=') (string / [-a-zA-Z_0-9]+) ']' / '[' (tag / string) ']'
258
+ end
259
+
260
+ rule class
261
+ '.' [_a-zA-Z] [-a-zA-Z0-9_]*
262
+ end
263
+
264
+ rule id
265
+ '#' [_a-zA-Z] [-a-zA-Z0-9_]*
266
+ end
267
+
268
+ rule tag
269
+ [a-zA-Z] [-a-zA-Z]* [0-9]? / '*'
270
+ end
271
+
272
+ rule select
273
+ (s [+>~] s / '::' / s ':' / S)?
274
+ end
275
+
276
+ # TODO: Merge this with attribute rule
277
+ rule accessor
278
+ ident:(class / id / tag) '[' attr:(string / variable) ']' {
279
+ def build env
280
+ env.nearest(ident.text_value)[attr.text_value.delete(%q["'])].evaluate
281
+ end
282
+ }
283
+ end
284
+
285
+ rule operator
286
+ S [-+*/] S {
287
+ def build env
288
+ Node::Operator.new(text_value.strip)
289
+ end
290
+ } / [-+*/] {
291
+ def build env
292
+ Node::Operator.new(text_value)
293
+ end
294
+ }
295
+ end
296
+
297
+ #
298
+ # Functions and arguments
299
+ #
300
+ rule function
301
+ name:([-a-zA-Z_]+) arguments {
302
+ def build
303
+ Node::Function.new(name.text_value, arguments.build)
304
+ end
305
+ }
306
+ end
307
+
308
+ rule arguments
309
+ '(' s expressions s tail:(',' s expressions s)* ')' {
310
+ def build
311
+ all.map do |e|
312
+ e.build if e.respond_to? :build
313
+ end.compact
314
+ end
315
+
316
+ def all
317
+ [expressions] + tail.elements.map {|e| e.expressions }
318
+ end
319
+ } / '(' s ')' {
320
+ def build
321
+ []
322
+ end
323
+ }
324
+ end
325
+ end
326
+ end