y2r 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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