y2r 1.0.0

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.
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 SUSE LLC
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,145 @@
1
+ Y2R
2
+ ===
3
+
4
+ Y2R is a transpiler translating
5
+ [YCP](http://doc.opensuse.org/projects/YaST/SLES10/tdg/Book-YCPLanguage.html) (a
6
+ legacy language used to write parts of
7
+ [YaST](http://en.opensuse.org/Portal:YaST)) into Ruby. It will be used to
8
+ translate YCP-based parts of the YaST codebase into Ruby, which will allow us to
9
+ get rid of YCP completely.
10
+
11
+ The translation itself is described by a
12
+ [specification](https://github.com/yast/y2r/blob/master/spec/y2r_spec.md).
13
+
14
+ Installation
15
+ ------------
16
+
17
+ Y2R is tested only on [openSUSE 12.3](http://en.opensuse.org/Portal:12.3). It
18
+ probably won't work in other openSUSE versions, other Linux distributions, or
19
+ other operating systems.
20
+
21
+ The following steps will make YCP Killer run on a vanilla openSUSE 12.3 system.
22
+
23
+ 1. **Install Git**
24
+
25
+ $ sudo zypper in git
26
+
27
+ 2. **Update `ycpc`**
28
+
29
+ Updated `ycpc` is needed because Y2R uses it internally and it relies on
30
+ some features that are not present in `ycpc` bundled with openSUSE 12.3.
31
+
32
+ To install updated `ycpc`, install the `yast2-core` package from
33
+ `YaST:Head:ruby`:
34
+
35
+ $ sudo zypper ar -f \
36
+ http://download.opensuse.org/repositories/YaST:/Head:/ruby/openSUSE_12.3/ \
37
+ YaST:Head:ruby
38
+ $ sudo zypper in -f -r YaST:Head:ruby yast2-core
39
+
40
+ 3. **Install basic Ruby environment**
41
+
42
+ $ sudo zypper in ruby ruby-devel
43
+
44
+ 4. **Install Y2R's dependencies**
45
+
46
+ $ sudo zypper in gcc-c++ make libxml2-devel libxslt-devel # Needed by Nokogiri
47
+
48
+ 5. **Install Y2R**
49
+
50
+ $ sudo gem install y2r
51
+
52
+ 6. **Done!**
53
+
54
+ You can now start using Y2R.
55
+
56
+ Usage
57
+ -----
58
+
59
+ Y2R is a command-line tool. You can invoke it using the `y2r` command. Its
60
+ syntax looks like this:
61
+
62
+ y2r [options] [<ycp_file>] [<ruby_file>]
63
+
64
+ Y2R reads YCP code from `ycp_file`, generates Ruby code from it and writes it to
65
+ `ruby_file`. If `ruby_file` is omitted, its name is generated by changing
66
+ `ycp_file` extension to `.rb`. If both `ycp_file` and `ruby_file` are omitted,
67
+ standard input and output are used.
68
+
69
+ ### Options
70
+
71
+ Y2R supports the following options:
72
+
73
+ * **-I, --include-path \<path\>**
74
+
75
+ Path where to find included files. Can be specified multiple times.
76
+
77
+ Paths specified using this option are passed to `ycpc`, which is invoked as
78
+ part of the compilation.
79
+
80
+ * **-M, --module-path \<path\>**
81
+
82
+ Path where to find modules. Can be specified multiple times.
83
+
84
+ Paths specified using this option are passed to `ycpc`, which is invoked as
85
+ part of the compilation.
86
+
87
+ * **--export-private**
88
+
89
+ Export also private symbols from translated modules.
90
+
91
+ This option is needed because testuites of some YaST modules access also
92
+ private symbols of various YCP modules. As a result, these YCP modules need
93
+ to be translated with `--export-private`.
94
+
95
+ * **--as-include-file**
96
+
97
+ Compile as include file.
98
+
99
+ This option is needed because there is no way to tell a YCP containing a
100
+ client from one containing an include.
101
+
102
+ * **--extract-file \<file\>**
103
+
104
+ Compile only code of specified include file.
105
+
106
+ This option is useful when you need to compile an include file which is not
107
+ standalone. The solution is to compile it in context of some other file
108
+ (which supplies symbols the include depends on) and use `--extract-file` to
109
+ make sure code corresponding to the include is emitted instead of code
110
+ corresponding to the wrapping file.
111
+
112
+ The file needs to be specified in exactly the same way as in the `include`
113
+ statement in the wrapping file. For example, if the wrapping file contains
114
+
115
+ include "packages/common.ycp"
116
+
117
+ you need to specify `--extract-file packages/common.ycp`.
118
+
119
+ * **--report-file \<file\>**
120
+
121
+ Report specified file as the compiled one.
122
+
123
+ The reported file is used to construct some class names in generated code.
124
+
125
+ * **--xml**
126
+
127
+ Print just XML emitted by `ycpc`.
128
+
129
+ This option is useful mainly for debugging.
130
+
131
+ * **--version**
132
+
133
+ Print version information and exit.
134
+
135
+ * **--help**
136
+
137
+ Print help and exit.
138
+
139
+ Known Issues
140
+ ------------
141
+
142
+ * The code quality isn't optimal in many places. This was caused by very short
143
+ development time with little room for refactoring and cleanups. Given that
144
+ Y2R will be used just once and then mostly forgotten, this is not a big
145
+ deal.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/bin/y2r ADDED
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: utf-8
3
+
4
+ require "docopt"
5
+
6
+ require File.expand_path(File.dirname(__FILE__) + "/../lib/y2r")
7
+
8
+ doc = <<-EOT
9
+ Y2R -- transpiler translating YCP code into Ruby
10
+
11
+ Usage: y2r [--include-path <path> ...] [--module-path <path> ...] [options]
12
+ [<ycp_file>] [<ruby_file>]
13
+
14
+ Options:
15
+ -I, --include-path <path> Path where to find included files. Can be specified
16
+ multiple times.
17
+ -M, --module-path <path> Path where to find modules. Can be specified
18
+ multiple times.
19
+ --export-private Export also private symbols from translated modules.
20
+ --as-include-file Compile as include file.
21
+ --extract-file <file> Compile only code of specified included file.
22
+ --report-file <file> Report specified file as the compiled one.
23
+ -x, --xml Print just XML emitted by ycpc.
24
+ -v, --version Print version information and exit.
25
+ -h, --help Print help and exit.
26
+ EOT
27
+
28
+ begin
29
+ options = Docopt::docopt(doc, :help => true, :version => Y2R::VERSION)
30
+
31
+ ycp_file = options["<ycp_file>"]
32
+ ruby_file = options["<ruby_file>"]
33
+
34
+ if !ycp_file && !ruby_file
35
+ input_stream = $stdin
36
+ output_stream = $stdout
37
+ else
38
+ if !ruby_file
39
+ extension = options["--xml"] ? ".xml" : ".rb"
40
+ ruby_file = ycp_file.sub(/\.[^.]*$/, extension)
41
+ end
42
+
43
+ input_stream = File.open(ycp_file, "r")
44
+ output_stream = File.open(ruby_file, "w")
45
+ end
46
+
47
+ module_paths = if options["--module-path"]
48
+ options["--module-path"]
49
+ else
50
+ ENV["Y2R_MODULE_PATH"] ? ENV["Y2R_MODULE_PATH"].split(":") : nil
51
+ end
52
+
53
+ include_paths = if options["--include-path"]
54
+ options["--include-path"]
55
+ else
56
+ ENV["Y2R_INCLUDE_PATH"] ? ENV["Y2R_INCLUDE_PATH"].split(":") : nil
57
+ end
58
+
59
+ compile_options = {
60
+ :module_paths => module_paths,
61
+ :include_paths => include_paths,
62
+ :export_private => options["--export-private"],
63
+ :as_include_file => options["--as-include-file"],
64
+ :extracted_file => options["--extract-file"],
65
+ :reported_file => options["--report-file"],
66
+ :xml => options["--xml"],
67
+ :filename => ycp_file
68
+ }
69
+
70
+ begin
71
+ output_stream.write(Y2R.compile(input_stream.read, compile_options))
72
+ output_stream.write("\n")
73
+ rescue Y2R::Parser::SyntaxError, NotImplementedError => e
74
+ $stderr.puts e.message
75
+ exit 1
76
+ ensure
77
+ input_stream.close
78
+ output_stream.close
79
+ end
80
+ rescue Docopt::Exit => e
81
+ $stderr.puts e.message
82
+ exit 1
83
+ end
@@ -0,0 +1,30 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path(File.dirname(__FILE__) + "/y2r/ast/ruby")
4
+ require File.expand_path(File.dirname(__FILE__) + "/y2r/ast/ycp")
5
+ require File.expand_path(File.dirname(__FILE__) + "/y2r/parser")
6
+ require File.expand_path(File.dirname(__FILE__) + "/y2r/version")
7
+
8
+ module Y2R
9
+ def self.compile(input, options = {})
10
+ ast = Parser.new(options).parse(input)
11
+
12
+ if !options[:xml]
13
+ ycp_context = AST::YCP::CompilerContext.new(
14
+ :blocks => [],
15
+ :whitespace => AST::YCP::Comments::Whitespace::DROP_ALL,
16
+ :options => options,
17
+ :elsif => false
18
+ )
19
+ ruby_context = AST::Ruby::EmitterContext.new(
20
+ :width => 80,
21
+ :shift => 0,
22
+ :priority => AST::Ruby::Priority::NONE
23
+ )
24
+
25
+ ast.compile(ycp_context).to_ruby(ruby_context)
26
+ else
27
+ ast
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,1713 @@
1
+ # encoding: utf-8
2
+
3
+ require "ostruct"
4
+
5
+ module Y2R
6
+ module AST
7
+ # Classes in this module represent Ruby AST nodes. Their main task is to
8
+ # serialize themselves into readable Ruby code using the |to_ruby| method.
9
+ #
10
+ # Note that these classes can't represent the whole Ruby language, only
11
+ # parts actually generated by Y2R.
12
+ module Ruby
13
+ # Code-related utilities.
14
+ module Code
15
+ class << self
16
+ def multi_line?(code)
17
+ !code.index("\n").nil?
18
+ end
19
+ end
20
+ end
21
+
22
+ # Operator priorities.
23
+ #
24
+ # Note the table is incomplete (because the AST is incomplete).
25
+ module Priority
26
+ ATOMIC = 15 # atomic expressions (e.g. literals)
27
+ UNARY = 14 # !, ~, + (unary)
28
+ POWER = 13 # **
29
+ UNARY_MINUS = 12 # - (unary)
30
+ MULTIPLY = 11 # *, /, %
31
+ ADD = 10 # +, -
32
+ SHIFT = 9 # <<, >>
33
+ BITWISE_AND = 8 # &
34
+ BITWISE_OR = 7 # |, ^
35
+ COMPARE = 6 # >, >=, <, <=
36
+ EQUAL = 5 # <=>, ==, ===, !=, =~, !~
37
+ LOGICAL_AND = 4 # &&
38
+ LOGICAL_OR = 3 # ||
39
+ TERNARY = 2 # ? :
40
+ ASSIGNMENT = 1 # =
41
+ NONE = 0 # lowest priority, nothing needs to be in parens
42
+ end
43
+
44
+ # Context passed to the #to_ruby and related methods on nodes.
45
+ class EmitterContext < OpenStruct
46
+ def indented(n)
47
+ context = dup
48
+ context.width -= n
49
+ context.shift = 0
50
+ context
51
+ end
52
+
53
+ def shifted(n)
54
+ context = dup
55
+ context.shift += n
56
+ context
57
+ end
58
+
59
+ def with_priority(priority)
60
+ context = dup
61
+ context.priority = priority
62
+ context
63
+ end
64
+
65
+ def with_max_key_width(max_key_width)
66
+ context = dup
67
+ context.max_key_width = max_key_width
68
+ context
69
+ end
70
+
71
+ def without_max_key_width
72
+ context = dup
73
+ context.max_key_width = nil
74
+ context
75
+ end
76
+
77
+ def with_trailer(trailer)
78
+ context = dup
79
+ context.trailer = trailer
80
+ context
81
+ end
82
+
83
+ def without_trailer
84
+ context = dup
85
+ context.trailer = nil
86
+ context
87
+ end
88
+ end
89
+
90
+ class Node < OpenStruct
91
+ INDENT_STEP = 2
92
+
93
+ def to_ruby(context)
94
+ wrap_code_with_comments context do |comments_context|
95
+ wrap_code_with_parens comments_context do |parens_context|
96
+ to_ruby_base(parens_context)
97
+ end
98
+ end
99
+ end
100
+
101
+ def single_line_width(context)
102
+ wrap_width_with_comments context do |comments_context|
103
+ wrap_width_with_parens comments_context do |parens_context|
104
+ single_line_width_base(parens_context)
105
+ end
106
+ end
107
+ end
108
+
109
+ def hates_to_stand_alone?
110
+ false
111
+ end
112
+
113
+ def starts_with_comment?
114
+ comment_before
115
+ end
116
+
117
+ def ends_with_comment?
118
+ comment_after
119
+ end
120
+
121
+ def has_comment?
122
+ starts_with_comment? || ends_with_comment?
123
+ end
124
+
125
+ def pass_trailer?
126
+ false
127
+ end
128
+
129
+ def ensure_separated
130
+ if comment_before && !comment_before.start_with?("\n")
131
+ self.comment_before = "\n#{comment_before}"
132
+ else
133
+ self.comment_before = ""
134
+ end
135
+ end
136
+
137
+ protected
138
+
139
+ def indented(node, context)
140
+ indent(node.to_ruby(context.indented(INDENT_STEP)))
141
+ end
142
+
143
+ def indent(s)
144
+ s.gsub(/^(?=.)/, " " * INDENT_STEP)
145
+ end
146
+
147
+ def combine
148
+ parts = []
149
+ yield parts
150
+ parts.join("\n")
151
+ end
152
+
153
+ def list(items, separator, context)
154
+ item_shift = 0
155
+ items.map do |item|
156
+ item_context = context.shifted(item_shift)
157
+ item_code = item.to_ruby(item_context)
158
+ item_shift += item_code.size + separator.size
159
+ item_code
160
+ end.join(separator)
161
+ end
162
+
163
+ def wrapped_line_list(items, opener, separator, closer, context)
164
+ combine do |parts|
165
+ parts << opener if opener
166
+ items[0..-2].each do |item|
167
+ parts << "#{indented(item, context.with_trailer(separator))}"
168
+ end
169
+ parts << "#{indented(items.last, context)}" unless items.empty?
170
+ parts << closer if closer
171
+ end
172
+ end
173
+
174
+ def header(keyword, expression, context)
175
+ # This whole thing is a bit hacky, but we can't help it if we don't
176
+ # want to rewrite how |indent| and |shift| work (which we don't this
177
+ # late).
178
+ #
179
+ # The basic idea is to pretend that the whole header is indented,
180
+ # emit the expression code, and than hack the first line.
181
+
182
+ expression_context = context.
183
+ indented(INDENT_STEP).
184
+ shifted(keyword.size + 1 - INDENT_STEP)
185
+ expression_code = indent(expression.to_ruby(expression_context))
186
+
187
+ "#{keyword} #{expression_code[INDENT_STEP..-1]}"
188
+ end
189
+
190
+ def list_single_line_width(items, separator_width, context)
191
+ items_width = items.
192
+ map { |i| i.single_line_width(context) }.
193
+ reduce(0, &:+)
194
+ separators_width = separator_width * [items.size - 1, 0].max
195
+
196
+ items_width + separators_width
197
+ end
198
+
199
+ def fits_current_line?(context)
200
+ single_line_width_base(context) <= context.width - context.shift
201
+ end
202
+
203
+ def enclose_in_parens?(context)
204
+ priority < context.priority
205
+ end
206
+
207
+ def wrap_code_with_parens(context)
208
+ if enclose_in_parens?(context)
209
+ if pass_trailer?
210
+ trailer = ")#{context.trailer}"
211
+ "(#{yield(context.shifted(1).with_trailer(trailer))}"
212
+ else
213
+ "(#{yield(context.shifted(1))})"
214
+ end
215
+ else
216
+ yield context
217
+ end
218
+ end
219
+
220
+ def wrap_code_with_comments(context)
221
+ code = if pass_trailer?
222
+ yield context
223
+ else
224
+ "#{yield(context.without_trailer)}#{context.trailer}"
225
+ end
226
+
227
+ code = "#{comment_before}\n#{code}" if comment_before
228
+ code = "#{code} #{comment_after}" if comment_after
229
+ code
230
+ end
231
+
232
+ def wrap_width_with_parens(context)
233
+ enclose_in_parens?(context) ? 1 + yield(context) + 1 : yield(context)
234
+ end
235
+
236
+ def wrap_width_with_comments(context)
237
+ width = yield(context)
238
+ width += Float::INFINITY if comment_before
239
+ width += 1 + comment_after.size if comment_after
240
+ width
241
+ end
242
+ end
243
+
244
+ # ===== Statements =====
245
+
246
+ class Program < Node
247
+ def to_ruby(context)
248
+ combine do |parts|
249
+ statements_code = wrap_code_with_comments context do |comments_context|
250
+ wrap_code_with_parens comments_context do |parens_context|
251
+ statements.to_ruby(parens_context.with_priority(priority))
252
+ end
253
+ end
254
+
255
+ parts << "# encoding: utf-8"
256
+ parts << ""
257
+ parts << statements_code
258
+ end
259
+ end
260
+
261
+ def single_line_width_base(context)
262
+ Float::INFINITY # always multiline
263
+ end
264
+
265
+ def priority
266
+ Priority::NONE
267
+ end
268
+
269
+ private
270
+
271
+ def format_comment
272
+ comment.gsub(/^.*$/) { |s| s.empty? ? "#" : "# #{s}" }
273
+ end
274
+ end
275
+
276
+ class Class < Node
277
+ def to_ruby_base(context)
278
+ superclass_shift = 6 + name.size + 3
279
+ superclass_context = context.
280
+ shifted(superclass_shift).
281
+ with_priority(priority)
282
+ superclass_code = superclass.to_ruby(superclass_context)
283
+
284
+ combine do |parts|
285
+ parts << "class #{name} < #{superclass_code}"
286
+ parts << indented(statements, context.with_priority(priority))
287
+ parts << "end"
288
+ end
289
+ end
290
+
291
+ def single_line_width_base(context)
292
+ Float::INFINITY # always multiline
293
+ end
294
+
295
+ def priority
296
+ Priority::NONE
297
+ end
298
+ end
299
+
300
+ class Module < Node
301
+ def to_ruby_base(context)
302
+ combine do |parts|
303
+ parts << "module #{name}"
304
+ parts << indented(statements, context.with_priority(priority))
305
+ parts << "end"
306
+ end
307
+ end
308
+
309
+ def single_line_width_base(context)
310
+ Float::INFINITY # always multiline
311
+ end
312
+
313
+ def priority
314
+ Priority::NONE
315
+ end
316
+ end
317
+
318
+ class Def < Node
319
+ def to_ruby_base(context)
320
+ args_shift = 4 + name.size
321
+ args_context = context.shifted(args_shift).with_priority(priority)
322
+ args_code = emit_args(args_context)
323
+
324
+ combine do |parts|
325
+ parts << "def #{name}#{args_code}"
326
+ parts << indented(statements, context.with_priority(priority))
327
+ parts << "end"
328
+ end
329
+ end
330
+
331
+ def single_line_width_base(context)
332
+ Float::INFINITY # always multiline
333
+ end
334
+
335
+ def priority
336
+ Priority::NONE
337
+ end
338
+
339
+ private
340
+
341
+ def args_have_comments?
342
+ args.any? { |a| a.has_comment? }
343
+ end
344
+
345
+ def emit_args(context)
346
+ if !args_have_comments?
347
+ emit_args_single_line(context)
348
+ else
349
+ emit_args_multi_line(context)
350
+ end
351
+ end
352
+
353
+ def emit_args_single_line(context)
354
+ if !args.empty?
355
+ "(#{list(args, ", ", context.shifted(1))})"
356
+ else
357
+ ""
358
+ end
359
+ end
360
+
361
+ def emit_args_multi_line(context)
362
+ wrapped_line_list(args, "(", ",", ")", context)
363
+ end
364
+ end
365
+
366
+ class Statements < Node
367
+ def to_ruby_base(context)
368
+ combine do |parts|
369
+ # The |compact| call is needed because some YCP AST nodes don't
370
+ # translate into anything, meaning their |compile| method will
371
+ # return |nil|. These |nil|s may end up in statement lists.
372
+ statements.compact.each do |statement|
373
+ parts << statement.to_ruby(context.with_priority(priority))
374
+ end
375
+ end
376
+ end
377
+
378
+ def single_line_width_base(context)
379
+ case statements.size
380
+ when 0
381
+ 0
382
+ when 1
383
+ statement_context = context.with_priority(priority)
384
+
385
+ statements.first.single_line_width(statement_context)
386
+ else
387
+ Float::INFINITY # always multiline
388
+ end
389
+ end
390
+
391
+ def priority
392
+ Priority::NONE
393
+ end
394
+
395
+ def starts_with_comment?
396
+ comment_before ||
397
+ (statements.size > 0 && statements.first.starts_with_comment?)
398
+ end
399
+
400
+ def ends_with_comment?
401
+ comment_after ||
402
+ (statements.size > 0 && statements.last.ends_with_comment?)
403
+ end
404
+ end
405
+
406
+ class Begin < Node
407
+ def to_ruby_base(context)
408
+ combine do |parts|
409
+ parts << "begin"
410
+ parts << indented(statements, context.with_priority(priority))
411
+ parts << "end"
412
+ end
413
+ end
414
+
415
+ def single_line_width_base(context)
416
+ Float::INFINITY # always multiline
417
+ end
418
+
419
+ def priority
420
+ Priority::NONE
421
+ end
422
+ end
423
+
424
+ class If < Node
425
+ def to_ruby_base(context)
426
+ if fits_current_line?(context) && !self.elsif &&
427
+ !has_line_breaking_comment?
428
+ to_ruby_base_single_line(context)
429
+ else
430
+ to_ruby_base_multi_line(context)
431
+ end
432
+ end
433
+
434
+ def single_line_width_base(context)
435
+ if !self.else && !has_line_breaking_comment?
436
+ inner_context = context.with_priority(priority)
437
+
438
+ then_width = self.then.single_line_width(inner_context)
439
+ condition_width = condition.single_line_width(inner_context)
440
+
441
+ then_width + 4 + condition_width
442
+ else
443
+ Float::INFINITY
444
+ end
445
+ end
446
+
447
+ def priority
448
+ Priority::NONE
449
+ end
450
+
451
+ private
452
+
453
+ def has_line_breaking_comment?
454
+ condition.starts_with_comment? || self.then.ends_with_comment?
455
+ end
456
+
457
+ def to_ruby_base_single_line(context)
458
+ then_code = self.then.to_ruby(context.with_priority(priority))
459
+
460
+ condition_shift = then_code.size + 4
461
+ condition_context = context.
462
+ shifted(condition_shift).
463
+ with_priority(priority)
464
+ condition_code = condition.to_ruby(condition_context)
465
+
466
+ "#{then_code} if #{condition_code}"
467
+ end
468
+
469
+ def to_ruby_base_multi_line(context)
470
+ inner_context = context.with_priority(priority)
471
+
472
+ combine do |parts|
473
+ if self.elsif
474
+ parts << header("elsif", condition, inner_context)
475
+ else
476
+ parts << header("if", condition, inner_context)
477
+ end
478
+ parts << indented(self.then, inner_context)
479
+ if self.else
480
+ if self.else.elsif
481
+ parts << self.else.to_ruby(inner_context)
482
+ else
483
+ parts << "else"
484
+ parts << indented(self.else, inner_context)
485
+ parts << "end"
486
+ end
487
+ else
488
+ parts << "end"
489
+ end
490
+ end
491
+ end
492
+ end
493
+
494
+ class Unless < Node
495
+ def to_ruby_base(context)
496
+ if fits_current_line?(context) && !has_line_breaking_comment?
497
+ to_ruby_base_single_line(context)
498
+ else
499
+ to_ruby_base_multi_line(context)
500
+ end
501
+ end
502
+
503
+ def single_line_width_base(context)
504
+ if !self.else && !has_line_breaking_comment?
505
+ inner_context = context.with_priority(priority)
506
+
507
+ then_width = self.then.single_line_width(inner_context)
508
+ condition_width = condition.single_line_width(inner_context)
509
+
510
+ then_width + 8 + condition_width
511
+ else
512
+ Float::INFINITY
513
+ end
514
+ end
515
+
516
+ def priority
517
+ Priority::NONE
518
+ end
519
+
520
+ private
521
+
522
+ def has_line_breaking_comment?
523
+ condition.starts_with_comment? || self.then.ends_with_comment?
524
+ end
525
+
526
+ def to_ruby_base_single_line(context)
527
+ then_code = self.then.to_ruby(context.with_priority(priority))
528
+
529
+ condition_shift = then_code.size + 8
530
+ condition_context = context.
531
+ shifted(condition_shift).
532
+ with_priority(priority)
533
+ condition_code = condition.to_ruby(condition_context)
534
+
535
+ "#{then_code} unless #{condition_code}"
536
+ end
537
+
538
+ def to_ruby_base_multi_line(context)
539
+ inner_context = context.with_priority(priority)
540
+
541
+ combine do |parts|
542
+ parts << header("unless", condition, inner_context)
543
+ parts << indented(self.then, inner_context)
544
+ if self.else
545
+ parts << "else"
546
+ parts << indented(self.else, inner_context)
547
+ end
548
+ parts << "end"
549
+ end
550
+ end
551
+ end
552
+
553
+ class Case < Node
554
+ def to_ruby_base(context)
555
+ inner_context = context.with_priority(priority)
556
+
557
+ combine do |parts|
558
+ parts << header("case", expression, inner_context)
559
+ whens.each do |whem|
560
+ parts << indented(whem, inner_context)
561
+ end
562
+ parts << indented(self.else, inner_context) if self.else
563
+ parts << "end"
564
+ end
565
+ end
566
+
567
+ def single_line_width_base(context)
568
+ Float::INFINITY # always multiline
569
+ end
570
+
571
+ def priority
572
+ Priority::NONE
573
+ end
574
+ end
575
+
576
+ class When < Node
577
+ def to_ruby_base(context)
578
+ if !values_have_comments?
579
+ to_ruby_base_single_line(context)
580
+ else
581
+ to_ruby_base_multi_line(context)
582
+ end
583
+ end
584
+
585
+ def single_line_width_base(context)
586
+ Float::INFINITY # always multiline
587
+ end
588
+
589
+ private
590
+
591
+ def values_have_comments?
592
+ values.any? { |v| v.has_comment? }
593
+ end
594
+
595
+ def to_ruby_base_single_line(context)
596
+ values_context = context.shifted(5).with_priority(priority)
597
+
598
+ combine do |parts|
599
+ parts << "when #{list(values, ", ", values_context)}"
600
+ parts << indented(body, context.with_priority(priority))
601
+ end
602
+ end
603
+
604
+ def to_ruby_base_multi_line(context)
605
+ inner_context = context.with_priority(priority)
606
+
607
+ combine do |parts|
608
+ parts << "when"
609
+ parts << wrapped_line_list(values, nil, ",", nil, inner_context)
610
+ parts << indented(body, inner_context)
611
+ end
612
+ end
613
+
614
+ def priority
615
+ Priority::NONE
616
+ end
617
+ end
618
+
619
+ class Else < Node
620
+ def to_ruby_base(context)
621
+ combine do |parts|
622
+ parts << "else"
623
+ parts << indented(body, context.with_priority(priority))
624
+ end
625
+ end
626
+
627
+ def single_line_width_base(context)
628
+ Float::INFINITY # always multiline
629
+ end
630
+
631
+ def priority
632
+ Priority::NONE
633
+ end
634
+ end
635
+
636
+ class While < Node
637
+ def to_ruby_base(context)
638
+ inner_context = context.with_priority(priority)
639
+
640
+ if !body.is_a?(Begin)
641
+ combine do |parts|
642
+ parts << header("while", condition, inner_context)
643
+ parts << indented(body, inner_context)
644
+ parts << "end"
645
+ end
646
+ else
647
+ body_code = body.to_ruby(inner_context)
648
+ condition_code = condition.to_ruby(inner_context)
649
+
650
+ "#{body_code} while #{condition_code}"
651
+ end
652
+ end
653
+
654
+ def single_line_width_base(context)
655
+ Float::INFINITY # always multiline
656
+ end
657
+
658
+ def priority
659
+ Priority::NONE
660
+ end
661
+ end
662
+
663
+ class Until < Node
664
+ def to_ruby_base(context)
665
+ inner_context = context.with_priority(priority)
666
+
667
+ if !body.is_a?(Begin)
668
+ combine do |parts|
669
+ parts << header("until", condition, inner_context)
670
+ parts << indented(body, inner_context)
671
+ parts << "end"
672
+ end
673
+ else
674
+ body_code = body.to_ruby(inner_context)
675
+ condition_code = condition.to_ruby(inner_context)
676
+
677
+ "#{body_code} until #{condition_code}"
678
+ end
679
+ end
680
+
681
+ def single_line_width_base(context)
682
+ Float::INFINITY # always multiline
683
+ end
684
+
685
+ def priority
686
+ Priority::NONE
687
+ end
688
+ end
689
+
690
+ class Break < Node
691
+ def to_ruby_base(context)
692
+ "break"
693
+ end
694
+
695
+ def single_line_width_base(context)
696
+ 5
697
+ end
698
+
699
+ def priority
700
+ Priority::NONE
701
+ end
702
+ end
703
+
704
+ class Next < Node
705
+ def to_ruby_base(context)
706
+ if !has_line_breaking_comment?
707
+ to_ruby_base_single_line(context)
708
+ else
709
+ to_ruby_base_multi_line(context)
710
+ end
711
+ end
712
+
713
+ def single_line_width_base(context)
714
+ if !has_line_breaking_comment
715
+ if value
716
+ 5 + value.single_line_width(context.with_priority(priority))
717
+ else
718
+ 4
719
+ end
720
+ else
721
+ Float::INFINITY
722
+ end
723
+ end
724
+
725
+ private
726
+
727
+ def has_line_breaking_comment?
728
+ value && value.starts_with_comment?
729
+ end
730
+
731
+ def to_ruby_base_single_line(context)
732
+ if value
733
+ "next #{value.to_ruby(context.shifted(5).with_priority(priority))}"
734
+ else
735
+ "next"
736
+ end
737
+ end
738
+
739
+ def to_ruby_base_multi_line(context)
740
+ combine do |parts|
741
+ parts << "next ("
742
+ parts << indented(value, context.with_priority(priority))
743
+ parts << ")"
744
+ end
745
+ end
746
+
747
+ def priority
748
+ Priority::NONE
749
+ end
750
+ end
751
+
752
+ class Return < Node
753
+ def to_ruby_base(context)
754
+ if !has_line_breaking_comment?
755
+ to_ruby_base_single_line(context)
756
+ else
757
+ to_ruby_base_multi_line(context)
758
+ end
759
+ end
760
+
761
+ def single_line_width_base(context)
762
+ if !has_line_breaking_comment
763
+ if value
764
+ 7 + value.single_line_width(context.with_priority(priority))
765
+ else
766
+ 6
767
+ end
768
+ else
769
+ Float::INFINITY
770
+ end
771
+ end
772
+
773
+ private
774
+
775
+ def has_line_breaking_comment?
776
+ value && value.starts_with_comment?
777
+ end
778
+
779
+ def to_ruby_base_single_line(context)
780
+ if value
781
+ "return #{value.to_ruby(context.shifted(7).with_priority(priority))}"
782
+ else
783
+ "return"
784
+ end
785
+ end
786
+
787
+ def to_ruby_base_multi_line(context)
788
+ combine do |parts|
789
+ parts << "return ("
790
+ parts << indented(value, context.with_priority(priority))
791
+ parts << ")"
792
+ end
793
+ end
794
+
795
+ def priority
796
+ Priority::NONE
797
+ end
798
+ end
799
+
800
+ # ===== Expressions =====
801
+
802
+ class Expressions < Node
803
+ def to_ruby_base(context)
804
+ if (fits_current_line?(context) && !expressions_have_comments?) || expressions.empty?
805
+ to_ruby_base_single_line(context)
806
+ else
807
+ to_ruby_base_multi_line(context)
808
+ end
809
+ end
810
+
811
+ def single_line_width_base(context)
812
+ if !expressions_have_comments?
813
+ expressions_context = context.with_priority(Priority::NONE)
814
+
815
+ 1 + list_single_line_width(expressions, 2, expressions_context) + 1
816
+ else
817
+ Float::INFINITY
818
+ end
819
+ end
820
+
821
+ def priority
822
+ Priority::ATOMIC
823
+ end
824
+
825
+ private
826
+
827
+ def expressions_have_comments?
828
+ expressions_have_comments = expressions.any? { |e| e.has_comment? }
829
+ end
830
+
831
+ def to_ruby_base_single_line(context)
832
+ expressions_context = context.shifted(1).with_priority(Priority::NONE)
833
+
834
+ "(#{list(expressions, "; ", expressions_context)})"
835
+ end
836
+
837
+ def to_ruby_base_multi_line(context)
838
+ expressions_context = context.with_priority(Priority::NONE)
839
+
840
+ wrapped_line_list(expressions, "(", ";", ")", expressions_context)
841
+ end
842
+ end
843
+
844
+ class Assignment < Node
845
+ def to_ruby_base(context)
846
+ if !has_line_breaking_comment?
847
+ to_ruby_base_single_line(context)
848
+ else
849
+ to_ruby_base_multi_line(context)
850
+ end
851
+ end
852
+
853
+ def single_line_width_base(context)
854
+ if !has_line_breaking_comment?
855
+ inner_context = context.with_priority(priority)
856
+
857
+ lhs_width = lhs.single_line_width(inner_context)
858
+ rhs_width = rhs.single_line_width(inner_context)
859
+
860
+ lhs_width + 3 + rhs_width
861
+ else
862
+ Float::INFINITY
863
+ end
864
+ end
865
+
866
+ def priority
867
+ Priority::ASSIGNMENT
868
+ end
869
+
870
+ def starts_with_comment?
871
+ comment_before || lhs.starts_with_comment?
872
+ end
873
+
874
+ def ends_with_comment?
875
+ comment_after || rhs.ends_with_comment?
876
+ end
877
+
878
+ def pass_trailer?
879
+ true
880
+ end
881
+
882
+ private
883
+
884
+ def has_line_breaking_comment?
885
+ lhs.ends_with_comment? || rhs.starts_with_comment?
886
+ end
887
+
888
+ def to_ruby_base_single_line(context)
889
+ lhs_context = context.without_trailer.with_priority(priority)
890
+ lhs_code = lhs.to_ruby(lhs_context)
891
+
892
+ rhs_shift = lhs_code.size + 3
893
+ rhs_context = context.shifted(rhs_shift).with_priority(priority)
894
+ rhs_code = rhs.to_ruby(rhs_context)
895
+
896
+ "#{lhs_code} = #{rhs_code}"
897
+ end
898
+
899
+ def to_ruby_base_multi_line(context)
900
+ lhs_context = context.with_trailer(" =").with_priority(priority)
901
+ lhs_code = lhs.to_ruby(lhs_context)
902
+
903
+ rhs_context = context.with_priority(priority)
904
+ rhs_code = indented(rhs, rhs_context)
905
+
906
+ combine do |parts|
907
+ parts << lhs_code
908
+ parts << rhs_code
909
+ end
910
+ end
911
+ end
912
+
913
+ class UnaryOperator < Node
914
+ OPS_TO_PRIORITIES = {
915
+ "!" => Priority::UNARY,
916
+ "~" => Priority::UNARY,
917
+ "+" => Priority::UNARY,
918
+ "-" => Priority::UNARY_MINUS
919
+ }
920
+
921
+ def to_ruby_base(context)
922
+ expressions_context = context.shifted(op.size).with_priority(priority)
923
+
924
+ "#{op}#{expression.to_ruby(expressions_context)}"
925
+ end
926
+
927
+ def single_line_width_base(context)
928
+ inner_context = context.with_priority(priority)
929
+
930
+ op.size + expression.single_line_width(inner_context)
931
+ end
932
+
933
+ def priority
934
+ OPS_TO_PRIORITIES[op]
935
+ end
936
+
937
+ def ends_with_comment?
938
+ comment_after || expression.ends_with_comment?
939
+ end
940
+
941
+ def pass_trailer?
942
+ true
943
+ end
944
+ end
945
+
946
+ class BinaryOperator < Node
947
+ OPS_TO_PRIORITIES = {
948
+ "**" => Priority::POWER,
949
+ "*" => Priority::MULTIPLY,
950
+ "/" => Priority::MULTIPLY,
951
+ "%" => Priority::MULTIPLY,
952
+ "+" => Priority::ADD,
953
+ "-" => Priority::ADD,
954
+ "<<" => Priority::SHIFT,
955
+ ">>" => Priority::SHIFT,
956
+ "&" => Priority::BITWISE_AND,
957
+ "|" => Priority::BITWISE_OR,
958
+ "^" => Priority::BITWISE_OR,
959
+ ">" => Priority::COMPARE,
960
+ ">=" => Priority::COMPARE,
961
+ "<" => Priority::COMPARE,
962
+ "<=" => Priority::COMPARE,
963
+ "<=>" => Priority::EQUAL,
964
+ "==" => Priority::EQUAL,
965
+ "===" => Priority::EQUAL,
966
+ "!=" => Priority::EQUAL,
967
+ "=~" => Priority::EQUAL,
968
+ "!~" => Priority::EQUAL,
969
+ "&&" => Priority::LOGICAL_AND,
970
+ "||" => Priority::LOGICAL_OR,
971
+ }
972
+
973
+ def to_ruby_base(context)
974
+ if (fits_current_line?(context) || rhs.hates_to_stand_alone?) &&
975
+ !has_line_breaking_comment?
976
+ to_ruby_base_single_line(context)
977
+ else
978
+ to_ruby_base_multi_line(context)
979
+ end
980
+ end
981
+
982
+ def single_line_width_base(context)
983
+ if !has_line_breaking_comment?
984
+ inner_context = context.with_priority(priority)
985
+
986
+ lhs_width = lhs.single_line_width(inner_context)
987
+ rhs_width = rhs.single_line_width(inner_context)
988
+
989
+ lhs_width + 1 + op.size + 1 + rhs_width
990
+ else
991
+ Float::INFINITY
992
+ end
993
+ end
994
+
995
+ def priority
996
+ OPS_TO_PRIORITIES[op]
997
+ end
998
+
999
+ def starts_with_comment?
1000
+ comment_before || lhs.starts_with_comment?
1001
+ end
1002
+
1003
+ def ends_with_comment?
1004
+ comment_after || rhs.ends_with_comment?
1005
+ end
1006
+
1007
+ def pass_trailer?
1008
+ true
1009
+ end
1010
+
1011
+ protected
1012
+
1013
+ def enclose_in_parens?(context)
1014
+ # YCP allows |a == b == c|, Ruby does not. We ensure |(a == b) == c|
1015
+ # gets emitted instead.
1016
+ if priority == Priority::EQUAL && context.priority == Priority::EQUAL
1017
+ true
1018
+ else
1019
+ super context
1020
+ end
1021
+ end
1022
+
1023
+ private
1024
+
1025
+ def has_line_breaking_comment?
1026
+ lhs.ends_with_comment? || rhs.starts_with_comment?
1027
+ end
1028
+
1029
+ def to_ruby_base_single_line(context)
1030
+ lhs_context = context.without_trailer.with_priority(priority)
1031
+ lhs_code = lhs.to_ruby(lhs_context)
1032
+
1033
+ rhs_shift = lhs_code.size + 1 + op.size + 1
1034
+ rhs_context = context.shifted(rhs_shift).with_priority(priority)
1035
+ rhs_code = rhs.to_ruby(rhs_context)
1036
+
1037
+ "#{lhs_code} #{op} #{rhs_code}"
1038
+ end
1039
+
1040
+ def to_ruby_base_multi_line(context)
1041
+ lhs_context = context.with_trailer(" #{op}").with_priority(priority)
1042
+ lhs_code = lhs.to_ruby(lhs_context)
1043
+
1044
+ rhs_context = context.with_priority(priority)
1045
+ rhs_code = indented(rhs, rhs_context)
1046
+
1047
+ combine do |parts|
1048
+ parts << lhs_code
1049
+ parts << rhs_code
1050
+ end
1051
+ end
1052
+ end
1053
+
1054
+ class TernaryOperator < Node
1055
+ def to_ruby_base(context)
1056
+ if (fits_current_line?(context) || (self.then.hates_to_stand_alone? && self.else.hates_to_stand_alone?)) &&
1057
+ !has_line_breaking_comment?
1058
+ to_ruby_base_single_line(context)
1059
+ else
1060
+ to_ruby_base_multi_line(context)
1061
+ end
1062
+ end
1063
+
1064
+ def single_line_width_base(context)
1065
+ if !has_line_breaking_comment?
1066
+ inner_context = context.with_priority(priority)
1067
+
1068
+ condition_width = condition.single_line_width(inner_context)
1069
+ then_width = self.then.single_line_width(inner_context)
1070
+ else_width = self.else.single_line_width(inner_context)
1071
+
1072
+ condition_width + 3 + then_width + 3 + else_width
1073
+ else
1074
+ Float::INFINITY
1075
+ end
1076
+ end
1077
+
1078
+ def priority
1079
+ Priority::TERNARY
1080
+ end
1081
+
1082
+ def starts_with_comment?
1083
+ comment_before || condition.starts_with_comment?
1084
+ end
1085
+
1086
+ def ends_with_comment?
1087
+ comment_after || self.else.ends_with_comment?
1088
+ end
1089
+
1090
+ def pass_trailer?
1091
+ true
1092
+ end
1093
+
1094
+ private
1095
+
1096
+ def has_line_breaking_comment?
1097
+ condition.ends_with_comment? ||
1098
+ self.then.starts_with_comment? ||
1099
+ self.then.ends_with_comment? ||
1100
+ self.else.starts_with_comment?
1101
+ end
1102
+
1103
+ def to_ruby_base_single_line(context)
1104
+ condition_context = context.without_trailer.with_priority(priority)
1105
+ condition_code = condition.to_ruby(condition_context)
1106
+
1107
+ then_shift = condition_code.size + 3
1108
+ then_context = context.
1109
+ shifted(then_shift).
1110
+ without_trailer.
1111
+ with_priority(priority)
1112
+ then_code = self.then.to_ruby(then_context)
1113
+
1114
+ else_shift = then_shift + then_code.size + 3
1115
+ else_context = context.shifted(else_shift).with_priority(priority)
1116
+ else_code = self.else.to_ruby(else_context)
1117
+
1118
+ "#{condition_code} ? #{then_code} : #{else_code}"
1119
+ end
1120
+
1121
+ def to_ruby_base_multi_line(context)
1122
+ condition_context = context.with_trailer(" ?").with_priority(priority)
1123
+ condition_code = condition.to_ruby(condition_context)
1124
+
1125
+ then_context = context.with_trailer(" :").with_priority(priority)
1126
+ then_code = indented(self.then, then_context)
1127
+
1128
+ else_context = context.with_priority(priority)
1129
+ else_code = indented(self.else, else_context)
1130
+
1131
+ combine do |parts|
1132
+ parts << condition_code
1133
+ parts << then_code
1134
+ parts << else_code
1135
+ end
1136
+ end
1137
+ end
1138
+
1139
+ class MethodCall < Node
1140
+ def to_ruby_base(context)
1141
+ # The algorithm for deciding whether the call should be split into
1142
+ # multile lines is based on seeing if the arguments fit into the
1143
+ # current line. That means we ignore the block part. This could lead
1144
+ # to some cases where a block starts beyond the right margin (when the
1145
+ # arguments fit, but just barely). I decided to ignore these cases, as
1146
+ # their impact on readability is minimal and handling them would
1147
+ # complicate already complex code.
1148
+
1149
+ receiver_context = context.with_trailer(".").with_priority(Priority::ATOMIC)
1150
+ receiver_code = receiver ? "#{receiver.to_ruby(receiver_context)}" : ""
1151
+
1152
+ if has_line_breaking_comment?
1153
+ context = context.indented(INDENT_STEP)
1154
+ end
1155
+
1156
+ args_shift = receiver_code.size + name.size
1157
+ args_context = context.
1158
+ shifted(args_shift).
1159
+ without_trailer.
1160
+ with_priority(Priority::NONE)
1161
+ args_code = emit_args(context, args_context)
1162
+
1163
+ if Code.multi_line?(args_code)
1164
+ block_context = context.shifted(1).with_priority(Priority::NONE)
1165
+ else
1166
+ block_shift = args_shift + args_code.size
1167
+ block_context = context.
1168
+ shifted(block_shift).
1169
+ with_priority(Priority::NONE)
1170
+ end
1171
+ block_code = block ? " #{block.to_ruby(block_context)}" : ""
1172
+
1173
+ if !has_line_breaking_comment?
1174
+ "#{receiver_code}#{name}#{args_code}#{block_code}"
1175
+ else
1176
+ combine do |parts|
1177
+ parts << receiver_code
1178
+ parts << indent("#{name}#{args_code}#{block_code}")
1179
+ end
1180
+ end
1181
+ end
1182
+
1183
+ def single_line_width_base(context)
1184
+ if !has_line_breaking_comment? && !args_have_comments?
1185
+ receiver_context = context.with_priority(Priority::ATOMIC)
1186
+ receiver_width = if receiver
1187
+ receiver.single_line_width(receiver_context) + 1
1188
+ else
1189
+ 0
1190
+ end
1191
+
1192
+ args_context = context.with_priority(Priority::NONE)
1193
+ args_width = args_single_line_width(args_context)
1194
+
1195
+ receiver_width + name.size + args_width
1196
+ else
1197
+ Float::INFINITY
1198
+ end
1199
+ end
1200
+
1201
+ def priority
1202
+ parens ? Priority::ATOMIC : Priority::NONE
1203
+ end
1204
+
1205
+ def starts_with_comment?
1206
+ if parens
1207
+ comment_before || (receiver && receiver.starts_with_comment?)
1208
+ else
1209
+ comment_before # Ignore deep comments, like we do for statements.
1210
+ end
1211
+ end
1212
+
1213
+ def ends_with_comment?
1214
+ if parens
1215
+ comment_after || (block && block.ends_with_comment?)
1216
+ else
1217
+ comment_after # Ignore deep comments, like we do for statements.
1218
+ end
1219
+ end
1220
+
1221
+ def pass_trailer?
1222
+ if parens
1223
+ block
1224
+ else
1225
+ false # Ignore deep comments, like we do for statements.
1226
+ end
1227
+ end
1228
+
1229
+ private
1230
+
1231
+ def has_line_breaking_comment?
1232
+ receiver && receiver.ends_with_comment?
1233
+ end
1234
+
1235
+ def args_have_comments?
1236
+ args.any? { |a| a.has_comment? }
1237
+ end
1238
+
1239
+ def emit_args(context, args_context)
1240
+ if (fits_current_line?(context) && !args_have_comments?) || !parens
1241
+ emit_args_single_line(args_context)
1242
+ else
1243
+ emit_args_multi_line(args_context)
1244
+ end
1245
+ end
1246
+
1247
+ def emit_args_single_line(context)
1248
+ if !args.empty?
1249
+ arg_context = context.without_max_key_width
1250
+
1251
+ if parens
1252
+ "(#{list(args, ", ", arg_context)})"
1253
+ else
1254
+ " #{list(args, ", ", arg_context)}"
1255
+ end
1256
+ else
1257
+ !receiver && name =~ /^[A-Z]/ && args.empty? ? "()" : ""
1258
+ end
1259
+ end
1260
+
1261
+ def emit_args_multi_line(context)
1262
+ entries = args.select { |a| a.is_a?(HashEntry) }
1263
+
1264
+ if !entries.empty?
1265
+ max_key_width = entries.map do |entry|
1266
+ entry.key_width(context.indented(INDENT_STEP))
1267
+ end.max
1268
+
1269
+ arg_context = context.with_max_key_width(max_key_width)
1270
+ else
1271
+ arg_context = context.without_max_key_width
1272
+ end
1273
+
1274
+ wrapped_line_list(args, "(", ",", ")", arg_context)
1275
+ end
1276
+
1277
+ def args_single_line_width(context)
1278
+ if !args.empty?
1279
+ if parens
1280
+ 1 + list_single_line_width(args, 2, context) + 1
1281
+ else
1282
+ 1 + list_single_line_width(args, 2, context)
1283
+ end
1284
+ else
1285
+ !receiver && name =~ /^[A-Z]/ && args.empty? ? 2 : 0
1286
+ end
1287
+ end
1288
+ end
1289
+
1290
+ class Block < Node
1291
+ def to_ruby_base(context)
1292
+ if fits_current_line?(context) && !args_have_comments?
1293
+ to_ruby_base_single_line(context)
1294
+ else
1295
+ to_ruby_base_multi_line(context)
1296
+ end
1297
+ end
1298
+
1299
+ def single_line_width_base(context)
1300
+ if !args_have_comments?
1301
+ args_context = context.with_priority(Priority::NONE)
1302
+ args_width = list_single_line_width(args, 2, args_context)
1303
+
1304
+ statements_context = context.with_priority(Priority::NONE)
1305
+ statements_width = statements.single_line_width(statements_context)
1306
+
1307
+ if !args.empty?
1308
+ 3 + args_width + 2 + statements_width + 2
1309
+ else
1310
+ 2 + statements_width + 2
1311
+ end
1312
+ else
1313
+ Float::INFINITY
1314
+ end
1315
+ end
1316
+
1317
+ def priority
1318
+ Priority::ATOMIC
1319
+ end
1320
+
1321
+ private
1322
+
1323
+ def args_have_comments?
1324
+ args.any? { |a| a.has_comment? }
1325
+ end
1326
+
1327
+ def to_ruby_base_single_line(context)
1328
+ args_context = context.shifted(1).with_priority(Priority::NONE)
1329
+ args_code = emit_args(args_context)
1330
+
1331
+ statements_shift = 1 + args_code.size + 1
1332
+ statements_context = context.
1333
+ shifted(statements_shift).
1334
+ with_priority(Priority::NONE)
1335
+ statements_code = statements.to_ruby(statements_context)
1336
+
1337
+ "{#{args_code} #{statements_code} }"
1338
+ end
1339
+
1340
+ def to_ruby_base_multi_line(context)
1341
+ args_context = context.shifted(2).with_priority(Priority::NONE)
1342
+ args_code = emit_args(args_context)
1343
+
1344
+ combine do |parts|
1345
+ parts << "do#{args_code}"
1346
+ parts << indented(statements, context.with_priority(Priority::NONE))
1347
+ parts << "end"
1348
+ end
1349
+ end
1350
+
1351
+ def emit_args(context)
1352
+ if !args_have_comments?
1353
+ emit_args_single_line(context)
1354
+ else
1355
+ emit_args_multi_line(context)
1356
+ end
1357
+ end
1358
+
1359
+ def emit_args_single_line(context)
1360
+ if !args.empty?
1361
+ " |#{list(args, ", ", context.shifted(2))}|"
1362
+ else
1363
+ ""
1364
+ end
1365
+ end
1366
+
1367
+ def emit_args_multi_line(context)
1368
+ wrapped_line_list(args, " |", ",", "|", context)
1369
+ end
1370
+ end
1371
+
1372
+ class ConstAccess < Node
1373
+ def to_ruby_base(context)
1374
+ if !has_line_breaking_comment?
1375
+ to_ruby_base_single_line(context)
1376
+ else
1377
+ to_ruby_base_multi_line(context)
1378
+ end
1379
+ end
1380
+
1381
+ def single_line_width_base(context)
1382
+ if receiver
1383
+ if !has_line_breaking_comment?
1384
+ receiver_context = context.with_priority(priority)
1385
+
1386
+ receiver.single_line_width(receiver_context) + 2 + name.size
1387
+ else
1388
+ Float::INFINITY
1389
+ end
1390
+ else
1391
+ name.size
1392
+ end
1393
+ end
1394
+
1395
+ def priority
1396
+ Priority::ATOMIC
1397
+ end
1398
+
1399
+ def starts_with_comment?
1400
+ comment_before || (receiver && receiver.starts_with_comment?)
1401
+ end
1402
+
1403
+ private
1404
+
1405
+ def has_line_breaking_comment?
1406
+ receiver && receiver.ends_with_comment?
1407
+ end
1408
+
1409
+ def to_ruby_base_single_line(context)
1410
+ if receiver
1411
+ "#{receiver.to_ruby(context.with_priority(priority))}::#{name}"
1412
+ else
1413
+ name
1414
+ end
1415
+ end
1416
+
1417
+ def to_ruby_base_multi_line(context)
1418
+ receiver_context = context.with_trailer("::").with_priority(priority)
1419
+ receiver_code = receiver.to_ruby(receiver_context)
1420
+
1421
+ combine do |parts|
1422
+ parts << receiver_code
1423
+ parts << indent(name)
1424
+ end
1425
+ end
1426
+ end
1427
+
1428
+ class Variable < Node
1429
+ def to_ruby_base(context)
1430
+ name
1431
+ end
1432
+
1433
+ def single_line_width_base(context)
1434
+ name.size
1435
+ end
1436
+
1437
+ def priority
1438
+ Priority::ATOMIC
1439
+ end
1440
+
1441
+ def hates_to_stand_alone?
1442
+ true
1443
+ end
1444
+ end
1445
+
1446
+ class Self < Node
1447
+ def to_ruby_base(context)
1448
+ "self"
1449
+ end
1450
+
1451
+ def single_line_width_base(context)
1452
+ 4
1453
+ end
1454
+
1455
+ def priority
1456
+ Priority::ATOMIC
1457
+ end
1458
+
1459
+ def hates_to_stand_alone?
1460
+ true
1461
+ end
1462
+ end
1463
+
1464
+ # ===== Literals =====
1465
+
1466
+ class Literal < Node
1467
+ def to_ruby_base(context)
1468
+ if is_long_multi_line_string?
1469
+ combine do |parts|
1470
+ lines = value.lines.to_a
1471
+
1472
+ parts << "#{lines.first.inspect} +"
1473
+ lines[1..-2].each do |line|
1474
+ parts << indent("#{line.inspect} +")
1475
+ end
1476
+ parts << indent("#{lines.last.inspect}")
1477
+ end
1478
+ else
1479
+ value.inspect
1480
+ end
1481
+ end
1482
+
1483
+ def single_line_width_base(context)
1484
+ if is_long_multi_line_string?
1485
+ Float::INFINITY
1486
+ else
1487
+ value.inspect.size
1488
+ end
1489
+ end
1490
+
1491
+ def priority
1492
+ if is_long_multi_line_string?
1493
+ Priority::ADD
1494
+ else
1495
+ Priority::ATOMIC
1496
+ end
1497
+ end
1498
+
1499
+ def hates_to_stand_alone?
1500
+ if value.is_a?(String) || value.is_a?(Symbol)
1501
+ value.size <= 16
1502
+ else
1503
+ true
1504
+ end
1505
+ end
1506
+
1507
+ private
1508
+
1509
+ def is_long_multi_line_string?
1510
+ value.is_a?(String) && value.size > 40 && value.lines.count > 2
1511
+ end
1512
+ end
1513
+
1514
+ class Array < Node
1515
+ def to_ruby_base(context)
1516
+ if (fits_current_line?(context) && !elements_have_comments?) || elements.empty?
1517
+ to_ruby_base_single_line(context)
1518
+ else
1519
+ to_ruby_base_multi_line(context)
1520
+ end
1521
+ end
1522
+
1523
+ def single_line_width_base(context)
1524
+ if !elements_have_comments?
1525
+ elements_context = context.with_priority(Priority::NONE)
1526
+
1527
+ 1 + list_single_line_width(elements, 2, elements_context) + 1
1528
+ else
1529
+ Float::INFINITY
1530
+ end
1531
+ end
1532
+
1533
+ def priority
1534
+ Priority::ATOMIC
1535
+ end
1536
+
1537
+ def hates_to_stand_alone?
1538
+ elements.empty?
1539
+ end
1540
+
1541
+ private
1542
+
1543
+ def elements_have_comments?
1544
+ elements.any? { |e| e.has_comment? }
1545
+ end
1546
+
1547
+ def to_ruby_base_single_line(context)
1548
+ elements_context = context.shifted(1).with_priority(Priority::NONE)
1549
+
1550
+ "[#{list(elements, ", ", elements_context)}]"
1551
+ end
1552
+
1553
+ def to_ruby_base_multi_line(context)
1554
+ elements_context = context.with_priority(Priority::NONE)
1555
+
1556
+ wrapped_line_list(elements, "[", ",", "]", elements_context)
1557
+ end
1558
+ end
1559
+
1560
+ class Hash < Node
1561
+ def to_ruby_base(context)
1562
+ if (fits_current_line?(context) && !entries_have_comments?) || entries.empty?
1563
+ to_ruby_base_single_line(context)
1564
+ else
1565
+ to_ruby_base_multi_line(context)
1566
+ end
1567
+ end
1568
+
1569
+ def single_line_width_base(context)
1570
+ if !entries.empty?
1571
+ if !entries_have_comments?
1572
+ entries_context = context.with_priority(Priority::NONE)
1573
+
1574
+ 2 + list_single_line_width(entries, 2, entries_context) + 2
1575
+ else
1576
+ Float::INFINITY
1577
+ end
1578
+ else
1579
+ 2
1580
+ end
1581
+ end
1582
+
1583
+ def priority
1584
+ Priority::ATOMIC
1585
+ end
1586
+
1587
+ def hates_to_stand_alone?
1588
+ entries.empty?
1589
+ end
1590
+
1591
+ private
1592
+
1593
+ def entries_have_comments?
1594
+ entries.any? { |e| e.has_comment? }
1595
+ end
1596
+
1597
+ def to_ruby_base_single_line(context)
1598
+ entry_context = context.
1599
+ shifted(2).
1600
+ with_priority(Priority::NONE).
1601
+ without_max_key_width
1602
+
1603
+ if !entries.empty?
1604
+ "{ #{list(entries, ", ", entry_context)} }"
1605
+ else
1606
+ "{}"
1607
+ end
1608
+ end
1609
+
1610
+ def to_ruby_base_multi_line(context)
1611
+ max_key_width = entries.map do |entry|
1612
+ entry.key_width(context.indented(INDENT_STEP))
1613
+ end.max
1614
+
1615
+ entry_context = context.
1616
+ with_priority(Priority::NONE).
1617
+ with_max_key_width(max_key_width)
1618
+
1619
+ wrapped_line_list(entries, "{", ",", "}", entry_context)
1620
+ end
1621
+ end
1622
+
1623
+ class HashEntry < Node
1624
+ def to_ruby_base(context)
1625
+ if !has_line_breaking_comment?
1626
+ to_ruby_base_single_line(context)
1627
+ else
1628
+ to_ruby_base_multi_line(context)
1629
+ end
1630
+ end
1631
+
1632
+ def single_line_width_base(context)
1633
+ if !has_line_breaking_comment?
1634
+ inner_context = context.with_priority(Priority::NONE)
1635
+
1636
+ key_width = key.single_line_width(inner_context)
1637
+ value_width = value.single_line_width(inner_context)
1638
+
1639
+ key_width + 4 + value_width
1640
+ else
1641
+ Float::INFINITY
1642
+ end
1643
+ end
1644
+
1645
+ def priority
1646
+ Priority::ATOMIC
1647
+ end
1648
+
1649
+ def starts_with_comment?
1650
+ comment_before || key.starts_with_comment?
1651
+ end
1652
+
1653
+ def ends_with_comment?
1654
+ comment_after || value.ends_with_comment?
1655
+ end
1656
+
1657
+ def pass_trailer?
1658
+ true
1659
+ end
1660
+
1661
+ def key_width(context)
1662
+ key.to_ruby_base(context).split("\n").last.size
1663
+ end
1664
+
1665
+ private
1666
+
1667
+ def has_line_breaking_comment?
1668
+ key.ends_with_comment? || value.starts_with_comment?
1669
+ end
1670
+
1671
+ def to_ruby_base_single_line(context)
1672
+ key_context = context.without_trailer.with_priority(Priority::NONE)
1673
+ key_code = key.to_ruby(key_context)
1674
+
1675
+ spacing_code = if context.max_key_width
1676
+ " " * (context.max_key_width - key_width(context))
1677
+ else
1678
+ ""
1679
+ end
1680
+
1681
+ value_shift = key_code.size + spacing_code.size + 4
1682
+ value_context = context.
1683
+ shifted(value_shift).
1684
+ with_priority(Priority::NONE)
1685
+ value_code = value.to_ruby(value_context)
1686
+
1687
+ "#{key_code}#{spacing_code} => #{value_code}"
1688
+ end
1689
+
1690
+ def to_ruby_base_multi_line(context)
1691
+ key_context = context.
1692
+ with_trailer("#{spacing_code} =>").
1693
+ with_priority(Priority::NONE)
1694
+ key_code = key.to_ruby(key_context)
1695
+
1696
+ spacing_code = if context.max_key_width
1697
+ " " * (context.max_key_width - key_width(context))
1698
+ else
1699
+ ""
1700
+ end
1701
+
1702
+ value_context = context.with_priority(Priority::NONE)
1703
+ value_code = indented(value, value_context)
1704
+
1705
+ combine do |parts|
1706
+ parts << key_code
1707
+ parts << value_code
1708
+ end
1709
+ end
1710
+ end
1711
+ end
1712
+ end
1713
+ end