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 +22 -0
- data/README.md +145 -0
- data/VERSION +1 -0
- data/bin/y2r +83 -0
- data/lib/y2r.rb +30 -0
- data/lib/y2r/ast/ruby.rb +1713 -0
- data/lib/y2r/ast/ycp.rb +2568 -0
- data/lib/y2r/parser.rb +709 -0
- data/lib/y2r/version.rb +5 -0
- metadata +140 -0
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.
|
data/README.md
ADDED
@@ -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
|
data/lib/y2r.rb
ADDED
@@ -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
|
data/lib/y2r/ast/ruby.rb
ADDED
@@ -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
|