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/lib/y2r/ast/ycp.rb
ADDED
@@ -0,0 +1,2568 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require "ostruct"
|
4
|
+
|
5
|
+
module Y2R
|
6
|
+
module AST
|
7
|
+
# Classes in this module represent YCP AST nodes. Their main taks is to
|
8
|
+
# compile themselves into Ruby AST nodes using the |compile| methods and its
|
9
|
+
# siblings (|compile_as_block|, etc.).
|
10
|
+
#
|
11
|
+
# The structure of the AST is heavily influenced by the structure of XML
|
12
|
+
# emitted by ycpc -x.
|
13
|
+
module YCP
|
14
|
+
# Compilation context passed to nodes' |compile| method. It mainly tracks
|
15
|
+
# the scope we're in and contains related helper methods.
|
16
|
+
class CompilerContext < OpenStruct
|
17
|
+
def at_toplevel?
|
18
|
+
!blocks.any?(&:creates_local_scope?)
|
19
|
+
end
|
20
|
+
|
21
|
+
def in?(klass)
|
22
|
+
blocks.find { |b| b.is_a?(klass) } ? true : false
|
23
|
+
end
|
24
|
+
|
25
|
+
def innermost(*klasses)
|
26
|
+
blocks.reverse.find { |b| klasses.any? { |k| b.is_a?(k) } }
|
27
|
+
end
|
28
|
+
|
29
|
+
def inside(block)
|
30
|
+
context = dup
|
31
|
+
context.blocks = blocks + [block]
|
32
|
+
|
33
|
+
yield context
|
34
|
+
end
|
35
|
+
|
36
|
+
def with_whitespace(whitespace)
|
37
|
+
context = dup
|
38
|
+
context.whitespace = whitespace
|
39
|
+
context
|
40
|
+
end
|
41
|
+
|
42
|
+
# elsif_mode is enabled iff the `If` node contain `If` node in its
|
43
|
+
# else branch. In this mode ifs are translated as `elsif`.
|
44
|
+
def enable_elsif
|
45
|
+
context = dup
|
46
|
+
context.elsif_mode = true
|
47
|
+
context
|
48
|
+
end
|
49
|
+
|
50
|
+
def disable_elsif
|
51
|
+
context = dup
|
52
|
+
context.elsif_mode = false
|
53
|
+
context
|
54
|
+
end
|
55
|
+
|
56
|
+
def module_name
|
57
|
+
blocks.first.name
|
58
|
+
end
|
59
|
+
|
60
|
+
def symbols
|
61
|
+
blocks.map { |b| b.symbols.map(&:name) }.flatten
|
62
|
+
end
|
63
|
+
|
64
|
+
def locals
|
65
|
+
index = blocks.index(&:creates_local_scope?) || blocks.length
|
66
|
+
blocks[index..-1].map { |b| b.symbols.map(&:name) }.flatten
|
67
|
+
end
|
68
|
+
|
69
|
+
def globals
|
70
|
+
index = blocks.index(&:creates_local_scope?) || blocks.length
|
71
|
+
blocks[0..index].map { |b| b.symbols.map(&:name) }.flatten
|
72
|
+
end
|
73
|
+
|
74
|
+
def symbol_for(name)
|
75
|
+
symbols = blocks.map { |b| b.symbols }.flatten
|
76
|
+
symbols.reverse.find { |s| s.name == name }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Represents a YCP type.
|
81
|
+
class Type
|
82
|
+
attr_reader :type
|
83
|
+
|
84
|
+
def initialize(type)
|
85
|
+
@type = type
|
86
|
+
end
|
87
|
+
|
88
|
+
def ==(other)
|
89
|
+
other.instance_of?(Type) && other.type == @type
|
90
|
+
end
|
91
|
+
|
92
|
+
def to_s
|
93
|
+
@type
|
94
|
+
end
|
95
|
+
|
96
|
+
def reference?
|
97
|
+
@type =~ /&$/
|
98
|
+
end
|
99
|
+
|
100
|
+
def no_const
|
101
|
+
@type =~ /^const / ? Type.new(@type.sub(/^const /, "")) : self
|
102
|
+
end
|
103
|
+
|
104
|
+
def needs_copy?
|
105
|
+
!IMMUTABLE_TYPES.include?(no_const) && !reference?
|
106
|
+
end
|
107
|
+
|
108
|
+
def arg_types
|
109
|
+
nesting_level = 0
|
110
|
+
|
111
|
+
# First, extract content of the parens with arguments. This is a bit
|
112
|
+
# tricky, as they don't have to be the first parens in the type
|
113
|
+
# specification. For example, a type of function returning a reference
|
114
|
+
# to a function returning integer looks like this:
|
115
|
+
#
|
116
|
+
# integer()()
|
117
|
+
#
|
118
|
+
in_parens = ""
|
119
|
+
@type.each_char do |ch|
|
120
|
+
case ch
|
121
|
+
when '('
|
122
|
+
in_parens = "" if nesting_level == 0
|
123
|
+
nesting_level += 1
|
124
|
+
when ')'
|
125
|
+
nesting_level -= 1
|
126
|
+
else
|
127
|
+
in_parens += ch
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
types = []
|
132
|
+
type = ""
|
133
|
+
in_parens.each_char do |ch|
|
134
|
+
case ch
|
135
|
+
when ","
|
136
|
+
if nesting_level == 0
|
137
|
+
types << type
|
138
|
+
type = ""
|
139
|
+
else
|
140
|
+
type += ch
|
141
|
+
end
|
142
|
+
|
143
|
+
when "(", "<"
|
144
|
+
nesting_level += 1
|
145
|
+
type += ch
|
146
|
+
|
147
|
+
when ")", ">"
|
148
|
+
nesting_level -= 1
|
149
|
+
type += ch
|
150
|
+
|
151
|
+
else
|
152
|
+
type += ch
|
153
|
+
end
|
154
|
+
end
|
155
|
+
types << type unless type.empty?
|
156
|
+
|
157
|
+
types.map { |t| Type.new(t.strip) }
|
158
|
+
end
|
159
|
+
|
160
|
+
BOOLEAN = Type.new("boolean")
|
161
|
+
INTEGER = Type.new("integer")
|
162
|
+
SYMBOL = Type.new("symbol")
|
163
|
+
STRING = Type.new("string")
|
164
|
+
PATH = Type.new("path")
|
165
|
+
|
166
|
+
IMMUTABLE_TYPES = [BOOLEAN, INTEGER, SYMBOL, STRING, PATH]
|
167
|
+
end
|
168
|
+
|
169
|
+
# Contains utility functions related to comment processing.
|
170
|
+
module Comments
|
171
|
+
COMMENT_SPLITTING_REGEXP = /
|
172
|
+
\#[^\n]*(\n|$) # one-line hash comment
|
173
|
+
|
|
174
|
+
\/\/[^\n]*(\n|$) # one-line slash comment
|
175
|
+
|
|
176
|
+
\/\* # multi-line comment
|
177
|
+
(
|
178
|
+
[^*]|\*(?!\/)
|
179
|
+
)*
|
180
|
+
\*\/
|
181
|
+
|
|
182
|
+
((?!\#|\/\/|\/\*).)+ # non-comment
|
183
|
+
/xm
|
184
|
+
|
185
|
+
YAST_TYPES_REGEXP = /(void|any|boolean|string|symbol|integer|float|term|path|byteblock|block\s*<.*>|list(\s*<.*>|)|map(\s*<.*>|))/
|
186
|
+
|
187
|
+
# Value of CompilerContext#whitespace.
|
188
|
+
class Whitespace < OpenStruct
|
189
|
+
def drop_before_above?
|
190
|
+
drop_before_above
|
191
|
+
end
|
192
|
+
|
193
|
+
def drop_before_below?
|
194
|
+
drop_before_below
|
195
|
+
end
|
196
|
+
|
197
|
+
def drop_after_above?
|
198
|
+
drop_after_above
|
199
|
+
end
|
200
|
+
|
201
|
+
def drop_after_below?
|
202
|
+
drop_after_below
|
203
|
+
end
|
204
|
+
|
205
|
+
KEEP_ALL = Whitespace.new
|
206
|
+
DROP_ALL = Whitespace.new(
|
207
|
+
:drop_before_above => true,
|
208
|
+
:drop_before_below => true,
|
209
|
+
:drop_after_above => true,
|
210
|
+
:drop_after_below => true
|
211
|
+
)
|
212
|
+
end
|
213
|
+
|
214
|
+
class << self
|
215
|
+
def process_comment_before(node, comment, options)
|
216
|
+
whitespace = options[:whitespace]
|
217
|
+
|
218
|
+
comment = fix_delimiters(node, comment)
|
219
|
+
comment = strip_leading_whitespace(comment)
|
220
|
+
comment = strip_trailing_whitespace(comment)
|
221
|
+
|
222
|
+
if whitespace.drop_before_above?
|
223
|
+
comment = drop_leading_empty_lines(comment)
|
224
|
+
end
|
225
|
+
|
226
|
+
if whitespace.drop_before_below?
|
227
|
+
comment = drop_trailing_empty_lines(comment)
|
228
|
+
else
|
229
|
+
# In many before comments, there is a line of whitespace caused by
|
230
|
+
# separation of the comment from the node it belongs to. For
|
231
|
+
# example, in this case, the comment and the node are separated by
|
232
|
+
# "\n ":
|
233
|
+
#
|
234
|
+
# {
|
235
|
+
# /* Comment */
|
236
|
+
# y2milestone("M1");
|
237
|
+
# }
|
238
|
+
#
|
239
|
+
# We need to remove such lines of whitespace (which are now empty
|
240
|
+
# because of whitespace stripping above), but not touch any
|
241
|
+
# additional ones).
|
242
|
+
comment = drop_trailing_empty_line(comment)
|
243
|
+
end
|
244
|
+
|
245
|
+
# In whitespace-dropping mode we want to remove empty comments
|
246
|
+
# completely. Note that returning "" instead of nil would not be
|
247
|
+
# enough, at that would cause adding a newline into the generated
|
248
|
+
# code at some places.
|
249
|
+
if whitespace.drop_before_above? || whitespace.drop_before_below?
|
250
|
+
comment = nil if comment.empty?
|
251
|
+
end
|
252
|
+
|
253
|
+
comment
|
254
|
+
end
|
255
|
+
|
256
|
+
def process_comment_after(node, comment, options)
|
257
|
+
whitespace = options[:whitespace]
|
258
|
+
|
259
|
+
comment = fix_delimiters(node, comment)
|
260
|
+
comment = strip_leading_whitespace(comment)
|
261
|
+
comment = strip_trailing_whitespace(comment)
|
262
|
+
|
263
|
+
if whitespace.drop_after_above?
|
264
|
+
comment = drop_leading_empty_lines(comment)
|
265
|
+
end
|
266
|
+
|
267
|
+
if whitespace.drop_after_below?
|
268
|
+
comment = drop_trailing_empty_lines(comment)
|
269
|
+
end
|
270
|
+
|
271
|
+
# In whitespace-dropping mode we want to remove empty comments
|
272
|
+
# completely. Note that returning "" instead of nil would not be
|
273
|
+
# enough, at that would cause adding a newline into the generated
|
274
|
+
# code at some places.
|
275
|
+
if whitespace.drop_after_above? || whitespace.drop_after_below?
|
276
|
+
comment = nil if comment.empty?
|
277
|
+
end
|
278
|
+
|
279
|
+
comment
|
280
|
+
end
|
281
|
+
|
282
|
+
private
|
283
|
+
|
284
|
+
def fix_delimiters(node, comment)
|
285
|
+
fixed_comment = ""
|
286
|
+
|
287
|
+
comment.scan(COMMENT_SPLITTING_REGEXP) do
|
288
|
+
segment = $&
|
289
|
+
prefix = $`.split("\n").last || ""
|
290
|
+
|
291
|
+
if segment =~ /\A\/\//
|
292
|
+
segment = fix_single_line_segment(node, segment)
|
293
|
+
elsif segment =~ /\A\/\*/
|
294
|
+
segment = fix_multi_line_segment(node, segment, prefix)
|
295
|
+
end
|
296
|
+
|
297
|
+
fixed_comment << segment
|
298
|
+
end
|
299
|
+
|
300
|
+
fixed_comment
|
301
|
+
end
|
302
|
+
|
303
|
+
def strip_leading_whitespace(s)
|
304
|
+
s.gsub(/^[ \t]+/, "")
|
305
|
+
end
|
306
|
+
|
307
|
+
def strip_trailing_whitespace(s)
|
308
|
+
s.gsub(/[ \t]+$/, "")
|
309
|
+
end
|
310
|
+
|
311
|
+
def drop_leading_empty_lines(s)
|
312
|
+
s.gsub(/\A\n*/, "")
|
313
|
+
end
|
314
|
+
|
315
|
+
def drop_trailing_empty_lines(s)
|
316
|
+
s.gsub(/\n*\z/, "")
|
317
|
+
end
|
318
|
+
|
319
|
+
def drop_trailing_empty_line(s)
|
320
|
+
s.sub(/\n\z/, "")
|
321
|
+
end
|
322
|
+
|
323
|
+
def fix_single_line_segment(node, segment)
|
324
|
+
segment.sub(/\A\/\//, "#")
|
325
|
+
end
|
326
|
+
|
327
|
+
# Converts YCP type name to Ruby type name.
|
328
|
+
def ycp_to_ruby_type(type)
|
329
|
+
# unknown type, no change
|
330
|
+
return type unless type.match "^#{YAST_TYPES_REGEXP}$"
|
331
|
+
|
332
|
+
# ruby class names start with upcase letter
|
333
|
+
upper_case_names = ["boolean", "string", "symbol", "float"]
|
334
|
+
upper_case_names.each do |upper_case_name|
|
335
|
+
type.gsub!(upper_case_name) { |s| s.capitalize }
|
336
|
+
end
|
337
|
+
|
338
|
+
# integer -> Fixnum
|
339
|
+
type.gsub! "integer", "Fixnum"
|
340
|
+
|
341
|
+
# any -> Object
|
342
|
+
# "Object" is actually not 100% correct as only some types make
|
343
|
+
# sense, but "any" would be even worse (does not exist in Ruby)
|
344
|
+
type.gsub! "any", "Object"
|
345
|
+
|
346
|
+
# list -> Array
|
347
|
+
type.gsub! /list\s*/, "Array"
|
348
|
+
|
349
|
+
# map -> Hash
|
350
|
+
# yard uses '=>' delimiter
|
351
|
+
type.gsub! /map(\s*<\s*(\S+)\s*,\s*(\S+)\s*>|)/ do
|
352
|
+
if $2 && $3
|
353
|
+
"Hash{#{$2} => #{$3}}"
|
354
|
+
else
|
355
|
+
"Hash"
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
# path -> Yast::Path
|
360
|
+
type.gsub! "path", "Yast::Path"
|
361
|
+
|
362
|
+
# term -> Yast::Term
|
363
|
+
type.gsub! "term", "Yast::Term"
|
364
|
+
|
365
|
+
# byteblock -> Yast::Byteblock
|
366
|
+
type.gsub! "byteblock", "Yast::Byteblock"
|
367
|
+
|
368
|
+
# block<type> -> Proc
|
369
|
+
type.gsub! /block\s*<.*>/, "Proc"
|
370
|
+
|
371
|
+
type
|
372
|
+
end
|
373
|
+
|
374
|
+
# Process the original doc comment so that it works with YARD.
|
375
|
+
def process_doc_comment(node, segment)
|
376
|
+
|
377
|
+
# remove colon after a tag (it is ignored by ycpdoc)
|
378
|
+
segment.gsub! /^(#\s+@\S+):/, "\\1"
|
379
|
+
|
380
|
+
# remove @short tags, just add an empty line to use it
|
381
|
+
# as the short description
|
382
|
+
segment.gsub! /^(#\s+)@short\s+(.*)$/, "\\1\\2\n#"
|
383
|
+
|
384
|
+
# remove @descr tags, not needed
|
385
|
+
segment.gsub! /^(#\s+)@descr\s+/, "\\1"
|
386
|
+
|
387
|
+
# add parameter type info
|
388
|
+
if node.args
|
389
|
+
node.args.each do |arg|
|
390
|
+
segment.gsub! /^(#\s+@param)(s|)\s+(#{YAST_TYPES_REGEXP}\s+|)#{arg.name}\b/,
|
391
|
+
"\\1 [#{ycp_to_ruby_type(arg.type.to_s)}] #{arg.name}"
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# @return(s) type -> @return [type], the type is optional
|
396
|
+
segment.gsub!(/^(#\s+@return)(s|)(\s+)(#{YAST_TYPES_REGEXP}|)/) do
|
397
|
+
if $4.empty?
|
398
|
+
"#{$1}#{$3}"
|
399
|
+
else
|
400
|
+
"#{$1}#{$3}[#{ycp_to_ruby_type($4)}]"
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
# @internal -> @api private
|
405
|
+
segment.gsub! /^(#\s+)@internal\b/, "\\1@api private"
|
406
|
+
|
407
|
+
# @stable -> @note stable
|
408
|
+
segment.gsub! /^(#\s+)@stable\b/,
|
409
|
+
"\\1@note This is a stable API function"
|
410
|
+
|
411
|
+
# @unstable -> @note unstable
|
412
|
+
segment.gsub! /^(#\s+)@unstable\b/,
|
413
|
+
"\\1@note This is an unstable API function and may change in the future"
|
414
|
+
|
415
|
+
# @screenshot -> 
|
416
|
+
# uses markdown syntax
|
417
|
+
segment.gsub! /^(#\s+)@screenshot\s+(\S+)/,
|
418
|
+
"\\1"
|
419
|
+
|
420
|
+
# @example_file -> {include:file:<file>}
|
421
|
+
segment.gsub!(/^(#\s+)@example_file\s+(\S+)/) do
|
422
|
+
"#{$1}Example file (#{$2}): {include:file:#{$2.gsub /\.ycp$/, '.rb'}}"
|
423
|
+
end
|
424
|
+
|
425
|
+
# @see Foo -> @see #Foo
|
426
|
+
# @see Foo() -> @see #Foo
|
427
|
+
# do not change if there are multiple words
|
428
|
+
# (likely it refers to something else than a function name)
|
429
|
+
segment.gsub! /^(#\s+)@see\s+(\S+)(\(\)|)\s*$/, "\\1@see #\\2"
|
430
|
+
|
431
|
+
# @ref function -> {#function}, can be present anywhere in the text
|
432
|
+
segment.gsub! /@ref\s+(#|)(\S+)/, "{#\\2}"
|
433
|
+
|
434
|
+
# @struct and @tuple -> " " (Extra indent to start a block)
|
435
|
+
# multiline tag, needs line processing
|
436
|
+
in_struct = false
|
437
|
+
ret = ""
|
438
|
+
segment.each_line do |line|
|
439
|
+
if line.match /^#\s+@(struct|tuple)/
|
440
|
+
in_struct = true
|
441
|
+
|
442
|
+
# add header
|
443
|
+
line.gsub! /^#\s+@struct(.*)$/, "#\n# **Structure:**\n#\n# \\1"
|
444
|
+
line.gsub! /^#\s+@tuple(.*)$/, "#\n# **Tuple:**\n#\n# \\1"
|
445
|
+
ret << line
|
446
|
+
else
|
447
|
+
if in_struct
|
448
|
+
# empty line or a new tag closes the tag
|
449
|
+
if line.match(/^#\s*$/) || line.match(/^#\s*@/)
|
450
|
+
in_struct = false
|
451
|
+
else
|
452
|
+
# indent the struct/tuple block
|
453
|
+
line.gsub! /^#(\s+.*)$/, "# \\1"
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
ret << line
|
458
|
+
end
|
459
|
+
end
|
460
|
+
|
461
|
+
ret
|
462
|
+
end
|
463
|
+
|
464
|
+
def fix_multi_line_segment(node, segment, prefix)
|
465
|
+
# The [^*] part is needed to exclude license comments, which often
|
466
|
+
# begin with a line of stars.
|
467
|
+
is_doc_comment = segment =~ /\A\/\*\*[^*]/
|
468
|
+
|
469
|
+
is_starred = segment =~ /
|
470
|
+
\A
|
471
|
+
\/\*.*(\n|$) # first line
|
472
|
+
(^[ \t]*\*.*(\n|$))* # remaining lines
|
473
|
+
\z
|
474
|
+
/x
|
475
|
+
|
476
|
+
is_first_line_empty = segment =~ /\A\/\*\*?[ \t]*$/
|
477
|
+
|
478
|
+
# Remove delimiters and associated whitespace & newline.
|
479
|
+
segment = if is_starred && !is_first_line_empty
|
480
|
+
if is_doc_comment
|
481
|
+
segment.sub(/\A\/\*/, "")
|
482
|
+
else
|
483
|
+
segment.sub(/\A\//, "")
|
484
|
+
end
|
485
|
+
else
|
486
|
+
if is_doc_comment
|
487
|
+
segment.sub(/\A\/\*\*[ \t]*\n?/, "")
|
488
|
+
else
|
489
|
+
segment.sub(/\A\/\*[ \t]*\n?/, "")
|
490
|
+
end
|
491
|
+
end
|
492
|
+
segment = segment.sub(/\n?[ \t]*\*\/\z/, "")
|
493
|
+
|
494
|
+
# Prepend "#" delimiters. Handle "starred" comments specially.
|
495
|
+
if is_starred
|
496
|
+
segment = segment.gsub(/^[ \t]*\*/, "#")
|
497
|
+
else
|
498
|
+
segment = segment.
|
499
|
+
gsub(/^#{Regexp.quote(prefix)}/, "").
|
500
|
+
gsub(/^/, "# ")
|
501
|
+
end
|
502
|
+
|
503
|
+
# Process doc comments.
|
504
|
+
segment = process_doc_comment(node, segment) if is_doc_comment
|
505
|
+
|
506
|
+
segment
|
507
|
+
end
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
511
|
+
# Contains utility functions related to Ruby variables.
|
512
|
+
module RubyVar
|
513
|
+
# Taken from Ruby's parse.y (for 1.9.3).
|
514
|
+
RUBY_KEYWORDS = [
|
515
|
+
"BEGIN",
|
516
|
+
"END",
|
517
|
+
"__ENCODING__",
|
518
|
+
"__FILE__",
|
519
|
+
"__LINE__",
|
520
|
+
"alias",
|
521
|
+
"and",
|
522
|
+
"begin",
|
523
|
+
"break",
|
524
|
+
"case",
|
525
|
+
"class",
|
526
|
+
"def",
|
527
|
+
"defined",
|
528
|
+
"do",
|
529
|
+
"else",
|
530
|
+
"elsif",
|
531
|
+
"end",
|
532
|
+
"ensure",
|
533
|
+
"false",
|
534
|
+
"for",
|
535
|
+
"if",
|
536
|
+
"in",
|
537
|
+
"module",
|
538
|
+
"next",
|
539
|
+
"nil",
|
540
|
+
"not",
|
541
|
+
"or",
|
542
|
+
"redo",
|
543
|
+
"rescue",
|
544
|
+
"retry",
|
545
|
+
"return",
|
546
|
+
"self",
|
547
|
+
"super",
|
548
|
+
"then",
|
549
|
+
"true",
|
550
|
+
"undef",
|
551
|
+
"unless",
|
552
|
+
"until",
|
553
|
+
"when",
|
554
|
+
"while",
|
555
|
+
"yield"
|
556
|
+
]
|
557
|
+
|
558
|
+
class << self
|
559
|
+
# Escapes a YCP variable name so that it is a valid Ruby local
|
560
|
+
# variable name.
|
561
|
+
#
|
562
|
+
# The escaping is constructed so that it can't create any collision
|
563
|
+
# between names. More precisely, for any distinct strings passed to
|
564
|
+
# this function the results will be also distinct.
|
565
|
+
def escape_local(name)
|
566
|
+
name.sub(/^(#{RUBY_KEYWORDS.join("|")}|[A-Z_].*)$/) { |s| "_#{s}" }
|
567
|
+
end
|
568
|
+
|
569
|
+
# Builds a Ruby AST node for a variable with given name in given
|
570
|
+
# context, doing all necessary escaping, de-aliasing, etc.
|
571
|
+
def for(ns, name, context, mode)
|
572
|
+
# In the XML, all global module variable references are qualified
|
573
|
+
# (e.g. "M::i"). This includes references to variables defined in
|
574
|
+
# this module. All other variable references are unqualified (e.g
|
575
|
+
# "i").
|
576
|
+
if ns
|
577
|
+
if ns == context.module_name
|
578
|
+
Ruby::Variable.new(:name => "@#{name}")
|
579
|
+
else
|
580
|
+
Ruby::MethodCall.new(
|
581
|
+
:receiver => Ruby::Variable.new(:name => ns),
|
582
|
+
:name => name,
|
583
|
+
:args => [],
|
584
|
+
:block => nil,
|
585
|
+
:parens => true
|
586
|
+
)
|
587
|
+
end
|
588
|
+
else
|
589
|
+
is_local = context.locals.include?(name)
|
590
|
+
variables = if is_local
|
591
|
+
context.locals
|
592
|
+
else
|
593
|
+
context.globals
|
594
|
+
end
|
595
|
+
|
596
|
+
# If there already is a variable with given name (coming from some
|
597
|
+
# parent scope), suffix the variable name with "2". If there are two
|
598
|
+
# such variables, suffix the name with "3". And so on.
|
599
|
+
#
|
600
|
+
# The loop is needed because we need to do the same check and maybe
|
601
|
+
# additional round(s) of suffixing also for suffixed variable names to
|
602
|
+
# prevent conflicts.
|
603
|
+
suffixed_name = name
|
604
|
+
begin
|
605
|
+
count = variables.select { |v| v == suffixed_name }.size
|
606
|
+
suffixed_name = suffixed_name + count.to_s if count > 1
|
607
|
+
end while count > 1
|
608
|
+
|
609
|
+
variable_name = if is_local
|
610
|
+
RubyVar.escape_local(suffixed_name)
|
611
|
+
else
|
612
|
+
"@#{suffixed_name}"
|
613
|
+
end
|
614
|
+
|
615
|
+
variable = Ruby::Variable.new(:name => variable_name)
|
616
|
+
|
617
|
+
case mode
|
618
|
+
when :in_code
|
619
|
+
symbol = context.symbol_for(name)
|
620
|
+
# The "symbol &&" part is needed only because of tests. The symbol
|
621
|
+
# should be always present in real-world situations.
|
622
|
+
if symbol && symbol.category == :reference
|
623
|
+
Ruby::MethodCall.new(
|
624
|
+
:receiver => variable,
|
625
|
+
:name => "value",
|
626
|
+
:args => [],
|
627
|
+
:block => nil,
|
628
|
+
:parens => true
|
629
|
+
)
|
630
|
+
else
|
631
|
+
variable
|
632
|
+
end
|
633
|
+
|
634
|
+
when :in_arg
|
635
|
+
variable
|
636
|
+
|
637
|
+
else
|
638
|
+
raise "Unknown mode: #{mode.inspect}."
|
639
|
+
end
|
640
|
+
end
|
641
|
+
end
|
642
|
+
end
|
643
|
+
end
|
644
|
+
|
645
|
+
class Node < OpenStruct
|
646
|
+
class << self
|
647
|
+
def transfers_comments(*names)
|
648
|
+
names.each do |name|
|
649
|
+
name_without_comments = :"#{name}_without_comments"
|
650
|
+
name_with_comments = :"#{name}_with_comments"
|
651
|
+
|
652
|
+
define_method name_with_comments do |context|
|
653
|
+
whitespace = context.whitespace
|
654
|
+
if context.whitespace != Comments::Whitespace::DROP_ALL
|
655
|
+
context = context.with_whitespace(Comments::Whitespace::DROP_ALL)
|
656
|
+
end
|
657
|
+
|
658
|
+
node = send(name_without_comments, context)
|
659
|
+
if node
|
660
|
+
if comment_before
|
661
|
+
processed_comment_before = Comments.process_comment_before(
|
662
|
+
self,
|
663
|
+
comment_before,
|
664
|
+
:whitespace => whitespace
|
665
|
+
)
|
666
|
+
if processed_comment_before
|
667
|
+
node.comment_before = processed_comment_before
|
668
|
+
end
|
669
|
+
end
|
670
|
+
|
671
|
+
if comment_after
|
672
|
+
processed_comment_after = Comments.process_comment_after(
|
673
|
+
self,
|
674
|
+
comment_after,
|
675
|
+
:whitespace => whitespace
|
676
|
+
)
|
677
|
+
if processed_comment_after
|
678
|
+
node.comment_after = processed_comment_after
|
679
|
+
end
|
680
|
+
end
|
681
|
+
end
|
682
|
+
node
|
683
|
+
end
|
684
|
+
|
685
|
+
alias_method name_without_comments, name
|
686
|
+
alias_method name, name_with_comments
|
687
|
+
end
|
688
|
+
end
|
689
|
+
end
|
690
|
+
|
691
|
+
def creates_local_scope?
|
692
|
+
false
|
693
|
+
end
|
694
|
+
|
695
|
+
# `Ops` exists because YCP does not have exceptions and nil propagates
|
696
|
+
# to operation results. If we use a Ruby operator where the YaST program
|
697
|
+
# can produce `nil`, we would crash with an exception. If we know that
|
698
|
+
# `nil` cannot be there, we ca use a plain ruby operator.
|
699
|
+
def never_nil?
|
700
|
+
false
|
701
|
+
end
|
702
|
+
|
703
|
+
def needs_copy?
|
704
|
+
false
|
705
|
+
end
|
706
|
+
|
707
|
+
# In Ruby, methods return value of last expresion if no return statement
|
708
|
+
# is encountered. To match YaST's behavior in this case (returning nil),
|
709
|
+
# we need to append nil at the end, unless we are sure some statement in
|
710
|
+
# the method always causes early return. These early returns are detected
|
711
|
+
# using this method.
|
712
|
+
def always_returns?
|
713
|
+
false
|
714
|
+
end
|
715
|
+
|
716
|
+
def compile_as_copy_if_needed(context)
|
717
|
+
compile(context)
|
718
|
+
end
|
719
|
+
|
720
|
+
def compile_statements(statements, context)
|
721
|
+
if statements
|
722
|
+
statements.compile(context)
|
723
|
+
else
|
724
|
+
Ruby::Statements.new(:statements => [])
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
def compile_statements_inside_block(statements, context)
|
729
|
+
context.inside self do |inner_context|
|
730
|
+
compile_statements(statements, inner_context)
|
731
|
+
end
|
732
|
+
end
|
733
|
+
|
734
|
+
def remove_duplicate_imports(statements)
|
735
|
+
seen_imports = []
|
736
|
+
|
737
|
+
statements.select do |statement|
|
738
|
+
if statement.is_a?(Import)
|
739
|
+
if seen_imports.include?(statement.name)
|
740
|
+
false
|
741
|
+
else
|
742
|
+
seen_imports << statement.name
|
743
|
+
true
|
744
|
+
end
|
745
|
+
else
|
746
|
+
true
|
747
|
+
end
|
748
|
+
end
|
749
|
+
end
|
750
|
+
|
751
|
+
def compile_statements_with_whitespace(statements, context)
|
752
|
+
# There is a duplicate import removal logic in ycpc, but it doesn't
|
753
|
+
# work for auto-iports such as UI. As a result, we need to do the
|
754
|
+
# deduplication again ourselves.
|
755
|
+
statements = remove_duplicate_imports(statements)
|
756
|
+
|
757
|
+
case statements.size
|
758
|
+
when 0
|
759
|
+
[]
|
760
|
+
|
761
|
+
when 1
|
762
|
+
statement_context = context.with_whitespace(Comments::Whitespace.new(
|
763
|
+
:drop_before_above => true,
|
764
|
+
:drop_after_below => true
|
765
|
+
))
|
766
|
+
|
767
|
+
[statements.first.compile(statement_context)]
|
768
|
+
else
|
769
|
+
first_context = context.with_whitespace(Comments::Whitespace.new(
|
770
|
+
:drop_before_above => true
|
771
|
+
))
|
772
|
+
middle_context = context.with_whitespace(Comments::Whitespace::KEEP_ALL)
|
773
|
+
last_context = context.with_whitespace(Comments::Whitespace.new(
|
774
|
+
:drop_after_below => true
|
775
|
+
))
|
776
|
+
|
777
|
+
[statements.first.compile(first_context)] +
|
778
|
+
statements[1..-2].map { |s| s.compile(middle_context) } +
|
779
|
+
[statements.last.compile(last_context)]
|
780
|
+
end
|
781
|
+
end
|
782
|
+
|
783
|
+
def optimize_last_statement(statements, klass)
|
784
|
+
if !statements.empty?
|
785
|
+
last = statements.last
|
786
|
+
|
787
|
+
last_optimized = if last.is_a?(klass)
|
788
|
+
value = last.value || Ruby::Literal.new(:value => nil)
|
789
|
+
|
790
|
+
# We can't optimize the |return| or |next| away if they have
|
791
|
+
# comments and we can't move them to the value because it has its
|
792
|
+
# own comments. (We don't want to mess with concatenating.)
|
793
|
+
can_optimize = true
|
794
|
+
can_optimize = false if last.comment_before && value.comment_before
|
795
|
+
can_optimize = false if last.comment_after && value.comment_after
|
796
|
+
|
797
|
+
if can_optimize
|
798
|
+
value.comment_before = last.comment_before if last.comment_before
|
799
|
+
value.comment_after = last.comment_after if last.comment_after
|
800
|
+
value
|
801
|
+
else
|
802
|
+
last
|
803
|
+
end
|
804
|
+
else
|
805
|
+
last
|
806
|
+
end
|
807
|
+
|
808
|
+
statements[0..-2] + [last_optimized]
|
809
|
+
else
|
810
|
+
[]
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
def optimize_return(statements)
|
815
|
+
optimize_last_statement(statements, Ruby::Return)
|
816
|
+
end
|
817
|
+
|
818
|
+
def optimize_next(statements)
|
819
|
+
optimize_last_statement(statements, Ruby::Next)
|
820
|
+
end
|
821
|
+
end
|
822
|
+
|
823
|
+
# Sorted alphabetically.
|
824
|
+
|
825
|
+
class Assign < Node
|
826
|
+
def compile(context)
|
827
|
+
Ruby::Assignment.new(
|
828
|
+
:lhs => RubyVar.for(ns, name, context, :in_code),
|
829
|
+
:rhs => child.compile_as_copy_if_needed(context)
|
830
|
+
)
|
831
|
+
end
|
832
|
+
|
833
|
+
transfers_comments :compile
|
834
|
+
end
|
835
|
+
|
836
|
+
class Bracket < Node
|
837
|
+
def compile(context)
|
838
|
+
Ruby::MethodCall.new(
|
839
|
+
:receiver => Ruby::Variable.new(:name => "Ops"),
|
840
|
+
:name => "set",
|
841
|
+
:args => [
|
842
|
+
entry.compile(context),
|
843
|
+
build_index(context),
|
844
|
+
rhs.compile(context),
|
845
|
+
],
|
846
|
+
:block => nil,
|
847
|
+
:parens => true
|
848
|
+
)
|
849
|
+
end
|
850
|
+
|
851
|
+
transfers_comments :compile
|
852
|
+
|
853
|
+
private
|
854
|
+
|
855
|
+
def build_index(context)
|
856
|
+
if arg.children.size == 1
|
857
|
+
arg.children.first.compile(context)
|
858
|
+
else
|
859
|
+
arg.compile(context)
|
860
|
+
end
|
861
|
+
end
|
862
|
+
end
|
863
|
+
|
864
|
+
class Break < Node
|
865
|
+
def compile(context)
|
866
|
+
case context.innermost(While, Do, Repeat, UnspecBlock, Case, Default)
|
867
|
+
when While, Do, Repeat
|
868
|
+
Ruby::Break.new
|
869
|
+
when UnspecBlock
|
870
|
+
Ruby::MethodCall.new(
|
871
|
+
:receiver => nil,
|
872
|
+
:name => "raise",
|
873
|
+
:args => [Ruby::Variable.new(:name => "Break")],
|
874
|
+
:block => nil,
|
875
|
+
:parens => false
|
876
|
+
)
|
877
|
+
when Case
|
878
|
+
raise NotImplementedError,
|
879
|
+
"Case with a break in the middle encountered. These are not supported."
|
880
|
+
when Default
|
881
|
+
raise NotImplementedError,
|
882
|
+
"Default with a break in the middle encountered. These are not supported."
|
883
|
+
else
|
884
|
+
raise "Misplaced \"break\" statement."
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
transfers_comments :compile
|
889
|
+
end
|
890
|
+
|
891
|
+
class Builtin < Node
|
892
|
+
def compile(context)
|
893
|
+
module_name = case ns
|
894
|
+
when "SCR"
|
895
|
+
"SCR"
|
896
|
+
when "WFM"
|
897
|
+
"WFM"
|
898
|
+
when "float"
|
899
|
+
"Builtins::Float"
|
900
|
+
when "list"
|
901
|
+
"Builtins::List"
|
902
|
+
when "multiset"
|
903
|
+
"Builtins::Multiset"
|
904
|
+
else
|
905
|
+
"Builtins"
|
906
|
+
end
|
907
|
+
|
908
|
+
Ruby::MethodCall.new(
|
909
|
+
:receiver => Ruby::Variable.new(:name => module_name),
|
910
|
+
:name => name,
|
911
|
+
:args => args.map { |a| a.compile(context) },
|
912
|
+
:block => block ? block.compile_as_block(context) : nil,
|
913
|
+
:parens => true
|
914
|
+
)
|
915
|
+
end
|
916
|
+
|
917
|
+
transfers_comments :compile
|
918
|
+
end
|
919
|
+
|
920
|
+
class Call < Node
|
921
|
+
def compile(context)
|
922
|
+
call = case category
|
923
|
+
when :function
|
924
|
+
if !ns && context.locals.include?(name)
|
925
|
+
Ruby::MethodCall.new(
|
926
|
+
:receiver => RubyVar.for(nil, name, context, :in_code),
|
927
|
+
:name => "call",
|
928
|
+
:args => args.map { |a| a.compile(context) },
|
929
|
+
:block => nil,
|
930
|
+
:parens => true
|
931
|
+
)
|
932
|
+
else
|
933
|
+
# In the XML, all module function calls are qualified (e.g.
|
934
|
+
# "M::i"). This includes call to functions defined in this
|
935
|
+
# module. The problem is that in generated Ruby code, the module
|
936
|
+
# namespace may not exist yet (e.g. when the function is called
|
937
|
+
# at module toplvel in YCP), so we have to omit it (which is OK,
|
938
|
+
# because then the call will be invoked on |self|, whish is
|
939
|
+
# always our module).
|
940
|
+
fixed_ns = ns == context.module_name ? nil : ns
|
941
|
+
receiver = if fixed_ns
|
942
|
+
Ruby::Variable.new(:name => fixed_ns)
|
943
|
+
else
|
944
|
+
nil
|
945
|
+
end
|
946
|
+
|
947
|
+
Ruby::MethodCall.new(
|
948
|
+
:receiver => receiver,
|
949
|
+
:name => name,
|
950
|
+
:args => args.map { |a| a.compile(context) },
|
951
|
+
:block => nil,
|
952
|
+
:parens => true
|
953
|
+
)
|
954
|
+
end
|
955
|
+
when :variable # function reference stored in variable
|
956
|
+
Ruby::MethodCall.new(
|
957
|
+
:receiver => RubyVar.for(ns, name, context, :in_code),
|
958
|
+
:name => "call",
|
959
|
+
:args => args.map { |a| a.compile(context) },
|
960
|
+
:block => nil,
|
961
|
+
:parens => true
|
962
|
+
)
|
963
|
+
else
|
964
|
+
raise "Unknown call category: #{category.inspect}."
|
965
|
+
end
|
966
|
+
|
967
|
+
reference_args_with_types = args.zip(type.arg_types).select do |arg, type|
|
968
|
+
type.reference?
|
969
|
+
end
|
970
|
+
|
971
|
+
if !reference_args_with_types.empty?
|
972
|
+
setters = reference_args_with_types.map do |arg, type|
|
973
|
+
arg.compile_as_setter(context)
|
974
|
+
end
|
975
|
+
getters = reference_args_with_types.map do |arg, type|
|
976
|
+
arg.compile_as_getter(context)
|
977
|
+
end
|
978
|
+
|
979
|
+
case result
|
980
|
+
when :used
|
981
|
+
result_var = Ruby::Variable.new(
|
982
|
+
:name => RubyVar.escape_local("#{name}_result")
|
983
|
+
)
|
984
|
+
|
985
|
+
Ruby::Expressions.new(
|
986
|
+
:expressions => [
|
987
|
+
*setters,
|
988
|
+
Ruby::Assignment.new(:lhs => result_var, :rhs => call),
|
989
|
+
*getters,
|
990
|
+
result_var
|
991
|
+
]
|
992
|
+
)
|
993
|
+
|
994
|
+
when :unused
|
995
|
+
Ruby::Statements.new(
|
996
|
+
:statements => [
|
997
|
+
*setters,
|
998
|
+
call,
|
999
|
+
*getters,
|
1000
|
+
]
|
1001
|
+
)
|
1002
|
+
|
1003
|
+
else
|
1004
|
+
raise "Unknown call result usage flag: #{result.inspect}."
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
else
|
1008
|
+
call
|
1009
|
+
end
|
1010
|
+
end
|
1011
|
+
|
1012
|
+
transfers_comments :compile
|
1013
|
+
end
|
1014
|
+
|
1015
|
+
class Case < Node
|
1016
|
+
def symbols
|
1017
|
+
[]
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
def compile(context)
|
1021
|
+
if body.statements.last.is_a?(Break)
|
1022
|
+
# The following dance is here because we want ot keep the AST nodes
|
1023
|
+
# immutable and thus avoid modifying their data.
|
1024
|
+
|
1025
|
+
body_without_break = body.dup
|
1026
|
+
body_without_break.statements = body.statements[0..-2]
|
1027
|
+
elsif body.always_returns?
|
1028
|
+
body_without_break = body
|
1029
|
+
else
|
1030
|
+
raise NotImplementedError,
|
1031
|
+
"Case without a break or return encountered. These are not supported."
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
context.inside self do |inner_context|
|
1035
|
+
Ruby::When.new(
|
1036
|
+
:values => values.map { |v| v.compile(inner_context) },
|
1037
|
+
:body => body_without_break.compile(inner_context)
|
1038
|
+
)
|
1039
|
+
end
|
1040
|
+
end
|
1041
|
+
|
1042
|
+
def always_returns?
|
1043
|
+
body.always_returns?
|
1044
|
+
end
|
1045
|
+
|
1046
|
+
transfers_comments :compile
|
1047
|
+
end
|
1048
|
+
|
1049
|
+
class Compare < Node
|
1050
|
+
OPS_TO_OPS = {
|
1051
|
+
"==" => "==",
|
1052
|
+
"!=" => "!="
|
1053
|
+
}
|
1054
|
+
|
1055
|
+
OPS_TO_METHODS = {
|
1056
|
+
"<" => "less_than",
|
1057
|
+
">" => "greater_than",
|
1058
|
+
"<=" => "less_or_equal",
|
1059
|
+
">=" => "greater_or_equal"
|
1060
|
+
}
|
1061
|
+
|
1062
|
+
def compile(context)
|
1063
|
+
if OPS_TO_OPS[op]
|
1064
|
+
Ruby::BinaryOperator.new(
|
1065
|
+
:op => OPS_TO_OPS[op],
|
1066
|
+
:lhs => lhs.compile(context),
|
1067
|
+
:rhs => rhs.compile(context)
|
1068
|
+
)
|
1069
|
+
elsif OPS_TO_METHODS[op]
|
1070
|
+
Ruby::MethodCall.new(
|
1071
|
+
:receiver => Ruby::Variable.new(:name => "Ops"),
|
1072
|
+
:name => OPS_TO_METHODS[op],
|
1073
|
+
:args => [lhs.compile(context), rhs.compile(context)],
|
1074
|
+
:block => nil,
|
1075
|
+
:parens => true
|
1076
|
+
)
|
1077
|
+
else
|
1078
|
+
raise "Unknown compare operator #{op}."
|
1079
|
+
end
|
1080
|
+
end
|
1081
|
+
|
1082
|
+
transfers_comments :compile
|
1083
|
+
end
|
1084
|
+
|
1085
|
+
class Const < Node
|
1086
|
+
def compile(context)
|
1087
|
+
case type
|
1088
|
+
when :void
|
1089
|
+
Ruby::Literal.new(:value => nil)
|
1090
|
+
when :bool
|
1091
|
+
case value
|
1092
|
+
when "true"
|
1093
|
+
Ruby::Literal.new(:value => true)
|
1094
|
+
when "false"
|
1095
|
+
Ruby::Literal.new(:value => false)
|
1096
|
+
else
|
1097
|
+
raise "Unknown boolean value: #{value.inspect}."
|
1098
|
+
end
|
1099
|
+
when :int
|
1100
|
+
Ruby::Literal.new(:value => value.to_i)
|
1101
|
+
when :float
|
1102
|
+
Ruby::Literal.new(:value => value.sub(/\.$/, ".0").to_f)
|
1103
|
+
when :symbol
|
1104
|
+
Ruby::Literal.new(:value => value.to_sym)
|
1105
|
+
when :string
|
1106
|
+
Ruby::Literal.new(:value => value)
|
1107
|
+
when :path
|
1108
|
+
Ruby::MethodCall.new(
|
1109
|
+
:receiver => nil,
|
1110
|
+
:name => "path",
|
1111
|
+
:args => [Ruby::Literal.new(:value => value)],
|
1112
|
+
:block => nil,
|
1113
|
+
:parens => true
|
1114
|
+
)
|
1115
|
+
else
|
1116
|
+
raise "Unknown const type: #{type.inspect}."
|
1117
|
+
end
|
1118
|
+
end
|
1119
|
+
|
1120
|
+
transfers_comments :compile
|
1121
|
+
|
1122
|
+
def never_nil?
|
1123
|
+
return type != :void
|
1124
|
+
end
|
1125
|
+
end
|
1126
|
+
|
1127
|
+
class Continue < Node
|
1128
|
+
def compile(context)
|
1129
|
+
Ruby::Next.new
|
1130
|
+
end
|
1131
|
+
|
1132
|
+
transfers_comments :compile
|
1133
|
+
end
|
1134
|
+
|
1135
|
+
class Default < Node
|
1136
|
+
def symbols
|
1137
|
+
[]
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
def compile(context)
|
1141
|
+
if body.statements.last.is_a?(Break)
|
1142
|
+
# The following dance is here because we want ot keep the AST nodes
|
1143
|
+
# immutable and thus avoid modifying their data.
|
1144
|
+
|
1145
|
+
body_without_break = body.dup
|
1146
|
+
body_without_break.statements = body.statements[0..-2]
|
1147
|
+
else
|
1148
|
+
body_without_break = body
|
1149
|
+
end
|
1150
|
+
|
1151
|
+
context.inside self do |inner_context|
|
1152
|
+
Ruby::Else.new(:body => body_without_break.compile(inner_context))
|
1153
|
+
end
|
1154
|
+
end
|
1155
|
+
|
1156
|
+
def always_returns?
|
1157
|
+
body.always_returns?
|
1158
|
+
end
|
1159
|
+
|
1160
|
+
transfers_comments :compile
|
1161
|
+
end
|
1162
|
+
|
1163
|
+
class DefBlock < Node
|
1164
|
+
def creates_local_scope?
|
1165
|
+
true
|
1166
|
+
end
|
1167
|
+
|
1168
|
+
def compile(context)
|
1169
|
+
context.inside self do |inner_context|
|
1170
|
+
Ruby::Statements.new(
|
1171
|
+
:statements => optimize_return(
|
1172
|
+
compile_statements_with_whitespace(statements, inner_context)
|
1173
|
+
)
|
1174
|
+
)
|
1175
|
+
end
|
1176
|
+
end
|
1177
|
+
|
1178
|
+
def always_returns?
|
1179
|
+
statements.any? { |s| s.always_returns? }
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
transfers_comments :compile
|
1183
|
+
end
|
1184
|
+
|
1185
|
+
class Do < Node
|
1186
|
+
def symbols
|
1187
|
+
[]
|
1188
|
+
end
|
1189
|
+
|
1190
|
+
def compile(context)
|
1191
|
+
Ruby::While.new(
|
1192
|
+
:condition => self.while.compile(context),
|
1193
|
+
:body => Ruby::Begin.new(
|
1194
|
+
:statements => compile_statements_inside_block(self.do, context)
|
1195
|
+
)
|
1196
|
+
)
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
transfers_comments :compile
|
1200
|
+
end
|
1201
|
+
|
1202
|
+
class Entry < Node
|
1203
|
+
def compile(context)
|
1204
|
+
RubyVar.for(ns, name, context, :in_code)
|
1205
|
+
end
|
1206
|
+
|
1207
|
+
def compile_as_ref(context)
|
1208
|
+
Ruby::Variable.new(:name => "#{name}_ref")
|
1209
|
+
end
|
1210
|
+
|
1211
|
+
transfers_comments :compile, :compile_as_ref
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
class FileBlock < Node
|
1215
|
+
def name
|
1216
|
+
nil
|
1217
|
+
end
|
1218
|
+
|
1219
|
+
def compile(context)
|
1220
|
+
class_statements = []
|
1221
|
+
|
1222
|
+
context.inside self do |inner_context|
|
1223
|
+
class_statements += build_main_def(inner_context)
|
1224
|
+
class_statements += build_other_defs(inner_context)
|
1225
|
+
end
|
1226
|
+
|
1227
|
+
Ruby::Program.new(
|
1228
|
+
:statements => Ruby::Statements.new(
|
1229
|
+
:statements => [
|
1230
|
+
Ruby::Module.new(
|
1231
|
+
:name => "Yast",
|
1232
|
+
:statements => Ruby::Class.new(
|
1233
|
+
:name => class_name,
|
1234
|
+
:superclass => Ruby::Variable.new(:name => "Client"),
|
1235
|
+
:statements => Ruby::Statements.new(
|
1236
|
+
:statements => class_statements
|
1237
|
+
)
|
1238
|
+
)
|
1239
|
+
),
|
1240
|
+
Ruby::MethodCall.new(
|
1241
|
+
:receiver => Ruby::MethodCall.new(
|
1242
|
+
:receiver => Ruby::ConstAccess.new(
|
1243
|
+
:receiver => Ruby::Variable.new(:name => "Yast"),
|
1244
|
+
:name => class_name
|
1245
|
+
),
|
1246
|
+
:name => "new",
|
1247
|
+
:args => [],
|
1248
|
+
:block => nil,
|
1249
|
+
:parens => true
|
1250
|
+
),
|
1251
|
+
:name => "main",
|
1252
|
+
:args => [],
|
1253
|
+
:block => nil,
|
1254
|
+
:parens => true,
|
1255
|
+
:comment_before => ""
|
1256
|
+
)
|
1257
|
+
]
|
1258
|
+
)
|
1259
|
+
)
|
1260
|
+
end
|
1261
|
+
|
1262
|
+
transfers_comments :compile
|
1263
|
+
|
1264
|
+
private
|
1265
|
+
|
1266
|
+
def class_name
|
1267
|
+
client_name = File.basename(filename).sub(/\.[^.]*$/, "")
|
1268
|
+
client_name.
|
1269
|
+
gsub(/^./) { |s| s.upcase }.
|
1270
|
+
gsub(/[_.-]./) { |s| s[1].upcase } + "Client"
|
1271
|
+
end
|
1272
|
+
|
1273
|
+
def fundef_statements
|
1274
|
+
statements.select { |s| s.is_a?(FunDef) }
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
def other_statements
|
1278
|
+
statements - fundef_statements
|
1279
|
+
end
|
1280
|
+
|
1281
|
+
def has_main_def?
|
1282
|
+
!other_statements.empty?
|
1283
|
+
end
|
1284
|
+
|
1285
|
+
def build_main_def(context)
|
1286
|
+
if has_main_def?
|
1287
|
+
main_statements = compile_statements_with_whitespace(
|
1288
|
+
other_statements,
|
1289
|
+
context
|
1290
|
+
)
|
1291
|
+
|
1292
|
+
unless other_statements.any? {|s| s.always_returns? }
|
1293
|
+
main_statements << Ruby::Literal.new(
|
1294
|
+
:value => nil,
|
1295
|
+
:comment_before => ""
|
1296
|
+
)
|
1297
|
+
end
|
1298
|
+
|
1299
|
+
[
|
1300
|
+
Ruby::Def.new(
|
1301
|
+
:name => "main",
|
1302
|
+
:args => [],
|
1303
|
+
:statements => Ruby::Statements.new(
|
1304
|
+
:statements => optimize_return(main_statements)
|
1305
|
+
)
|
1306
|
+
)
|
1307
|
+
]
|
1308
|
+
else
|
1309
|
+
[]
|
1310
|
+
end
|
1311
|
+
end
|
1312
|
+
|
1313
|
+
def build_other_defs(context)
|
1314
|
+
defs = compile_statements_with_whitespace(fundef_statements, context)
|
1315
|
+
|
1316
|
+
unless defs.empty?
|
1317
|
+
defs.first.ensure_separated if has_main_def?
|
1318
|
+
end
|
1319
|
+
|
1320
|
+
defs
|
1321
|
+
end
|
1322
|
+
end
|
1323
|
+
|
1324
|
+
class Filename < Node
|
1325
|
+
def compile(context)
|
1326
|
+
# Ignored because we don't care about filename information.
|
1327
|
+
end
|
1328
|
+
end
|
1329
|
+
|
1330
|
+
class FunDef < Node
|
1331
|
+
def compile(context)
|
1332
|
+
statements = block.compile(context)
|
1333
|
+
|
1334
|
+
context.inside block do |inner_context|
|
1335
|
+
statements.statements = args.select(&:needs_copy?).map do |arg|
|
1336
|
+
arg.compile_as_copy_arg_call(inner_context)
|
1337
|
+
end + statements.statements
|
1338
|
+
|
1339
|
+
unless block.always_returns?
|
1340
|
+
statements.statements << Ruby::Literal.new(
|
1341
|
+
:value => nil,
|
1342
|
+
:comment_before => ""
|
1343
|
+
)
|
1344
|
+
end
|
1345
|
+
|
1346
|
+
if !context.in?(DefBlock)
|
1347
|
+
Ruby::Def.new(
|
1348
|
+
:name => name,
|
1349
|
+
:args => args.map { |a| a.compile(inner_context) },
|
1350
|
+
:statements => statements
|
1351
|
+
)
|
1352
|
+
else
|
1353
|
+
Ruby::Assignment.new(
|
1354
|
+
:lhs => RubyVar.for(nil, name, context, :in_code),
|
1355
|
+
:rhs => Ruby::MethodCall.new(
|
1356
|
+
:receiver => nil,
|
1357
|
+
:name => "lambda",
|
1358
|
+
:args => [],
|
1359
|
+
:block => Ruby::Block.new(
|
1360
|
+
:args => args.map { |a| a.compile(inner_context) },
|
1361
|
+
:statements => statements
|
1362
|
+
),
|
1363
|
+
:parens => true
|
1364
|
+
)
|
1365
|
+
)
|
1366
|
+
end
|
1367
|
+
end
|
1368
|
+
end
|
1369
|
+
|
1370
|
+
transfers_comments :compile
|
1371
|
+
end
|
1372
|
+
|
1373
|
+
class If < Node
|
1374
|
+
def compile(context)
|
1375
|
+
then_context = context.disable_elsif
|
1376
|
+
then_compiled = compile_statements(self.then, then_context)
|
1377
|
+
|
1378
|
+
if self.else
|
1379
|
+
else_context = if self.else.is_a?(If)
|
1380
|
+
context.enable_elsif
|
1381
|
+
else
|
1382
|
+
context.disable_elsif
|
1383
|
+
end
|
1384
|
+
else_compiled = compile_statements(self.else, else_context)
|
1385
|
+
else
|
1386
|
+
else_compiled = nil
|
1387
|
+
end
|
1388
|
+
|
1389
|
+
Ruby::If.new(
|
1390
|
+
:condition => cond.compile(context),
|
1391
|
+
:then => then_compiled,
|
1392
|
+
:else => else_compiled,
|
1393
|
+
:elsif => !!context.elsif_mode
|
1394
|
+
)
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
def always_returns?
|
1398
|
+
if self.then && self.else
|
1399
|
+
self.then.always_returns? && self.else.always_returns?
|
1400
|
+
else
|
1401
|
+
# If there is just one branch present, execution can always
|
1402
|
+
# continue because the branch may not be taken.
|
1403
|
+
false
|
1404
|
+
end
|
1405
|
+
end
|
1406
|
+
|
1407
|
+
transfers_comments :compile
|
1408
|
+
end
|
1409
|
+
|
1410
|
+
class Import < Node
|
1411
|
+
def compile(context)
|
1412
|
+
# Using any SCR or WFM function results in an auto-import. We ignore
|
1413
|
+
# these auto-imports becasue neither SCR nor WFM are real modules.
|
1414
|
+
return nil if name == "SCR" || name == "WFM"
|
1415
|
+
|
1416
|
+
Ruby::MethodCall.new(
|
1417
|
+
:receiver => Ruby::Variable.new(:name => "Yast"),
|
1418
|
+
:name => "import",
|
1419
|
+
:args => [Ruby::Literal.new(:value => name)],
|
1420
|
+
:block => nil,
|
1421
|
+
:parens => false
|
1422
|
+
)
|
1423
|
+
end
|
1424
|
+
|
1425
|
+
transfers_comments :compile
|
1426
|
+
end
|
1427
|
+
|
1428
|
+
class Include < Node
|
1429
|
+
def compile(context)
|
1430
|
+
if !context.at_toplevel?
|
1431
|
+
raise NotImplementedError,
|
1432
|
+
"Non-toplevel includes are not supported."
|
1433
|
+
end
|
1434
|
+
|
1435
|
+
args = [
|
1436
|
+
if context.options[:as_include_file]
|
1437
|
+
Ruby::Variable.new(:name => "include_target")
|
1438
|
+
else
|
1439
|
+
Ruby::Self.new
|
1440
|
+
end,
|
1441
|
+
Ruby::Literal.new(:value => name.sub(/\.y(cp|h)$/, ".rb"))
|
1442
|
+
]
|
1443
|
+
|
1444
|
+
Ruby::MethodCall.new(
|
1445
|
+
:receiver => Ruby::Variable.new(:name => "Yast"),
|
1446
|
+
:name => "include",
|
1447
|
+
:args => args,
|
1448
|
+
:block => nil,
|
1449
|
+
:parens => false
|
1450
|
+
)
|
1451
|
+
end
|
1452
|
+
|
1453
|
+
transfers_comments :compile
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
class IncludeBlock < Node
|
1457
|
+
def compile(context)
|
1458
|
+
class_statements = []
|
1459
|
+
|
1460
|
+
context.inside self do |inner_context|
|
1461
|
+
class_statements += build_initialize_method_def(inner_context)
|
1462
|
+
class_statements += build_other_defs(inner_context)
|
1463
|
+
end
|
1464
|
+
|
1465
|
+
Ruby::Program.new(
|
1466
|
+
:statements => Ruby::Statements.new(
|
1467
|
+
:statements => [
|
1468
|
+
Ruby::Module.new(
|
1469
|
+
:name => "Yast",
|
1470
|
+
:statements => Ruby::Module.new(
|
1471
|
+
:name => module_name,
|
1472
|
+
:statements => Ruby::Statements.new(
|
1473
|
+
:statements => class_statements
|
1474
|
+
)
|
1475
|
+
)
|
1476
|
+
)
|
1477
|
+
]
|
1478
|
+
)
|
1479
|
+
)
|
1480
|
+
end
|
1481
|
+
|
1482
|
+
transfers_comments :compile
|
1483
|
+
|
1484
|
+
private
|
1485
|
+
|
1486
|
+
def module_name
|
1487
|
+
parts = path_parts.map do |part|
|
1488
|
+
part.
|
1489
|
+
gsub(/^./) { |s| s.upcase }.
|
1490
|
+
gsub(/[_.-]./) { |s| s[1].upcase }
|
1491
|
+
end
|
1492
|
+
|
1493
|
+
"#{parts.join("")}Include"
|
1494
|
+
end
|
1495
|
+
|
1496
|
+
def initialize_method_name
|
1497
|
+
parts = path_parts.map { |p| p.gsub(/[_.-]/, "_") }
|
1498
|
+
|
1499
|
+
"initialize_#{parts.join("_")}"
|
1500
|
+
end
|
1501
|
+
|
1502
|
+
def path_parts
|
1503
|
+
path = if filename =~ /src\/include\//
|
1504
|
+
filename.sub(/^.*src\/include\//, "")
|
1505
|
+
else
|
1506
|
+
File.basename(filename)
|
1507
|
+
end
|
1508
|
+
|
1509
|
+
path.sub(/\.y(cp|h)$/, "").split("/")
|
1510
|
+
end
|
1511
|
+
|
1512
|
+
def fundef_statements
|
1513
|
+
statements.select { |s| s.is_a?(FunDef) }
|
1514
|
+
end
|
1515
|
+
|
1516
|
+
def other_statements
|
1517
|
+
statements - fundef_statements
|
1518
|
+
end
|
1519
|
+
|
1520
|
+
def has_initialize_method_def?
|
1521
|
+
!other_statements.empty?
|
1522
|
+
end
|
1523
|
+
|
1524
|
+
def build_initialize_method_def(context)
|
1525
|
+
if has_initialize_method_def?
|
1526
|
+
initialize_method_statements = compile_statements_with_whitespace(
|
1527
|
+
other_statements,
|
1528
|
+
context
|
1529
|
+
)
|
1530
|
+
|
1531
|
+
[
|
1532
|
+
Ruby::Def.new(
|
1533
|
+
:name => initialize_method_name,
|
1534
|
+
:args => [Ruby::Variable.new(:name => "include_target")],
|
1535
|
+
:statements => Ruby::Statements.new(
|
1536
|
+
:statements => initialize_method_statements
|
1537
|
+
)
|
1538
|
+
)
|
1539
|
+
]
|
1540
|
+
else
|
1541
|
+
[]
|
1542
|
+
end
|
1543
|
+
end
|
1544
|
+
|
1545
|
+
def build_other_defs(context)
|
1546
|
+
defs = compile_statements_with_whitespace(fundef_statements, context)
|
1547
|
+
|
1548
|
+
unless defs.empty?
|
1549
|
+
defs.first.ensure_separated if has_initialize_method_def?
|
1550
|
+
end
|
1551
|
+
|
1552
|
+
defs
|
1553
|
+
end
|
1554
|
+
end
|
1555
|
+
|
1556
|
+
class List < Node
|
1557
|
+
def compile(context)
|
1558
|
+
Ruby::Array.new(
|
1559
|
+
:elements => children.map { |ch| ch.compile(context) }
|
1560
|
+
)
|
1561
|
+
end
|
1562
|
+
|
1563
|
+
transfers_comments :compile
|
1564
|
+
|
1565
|
+
def empty?
|
1566
|
+
children.empty?
|
1567
|
+
end
|
1568
|
+
end
|
1569
|
+
|
1570
|
+
class Locale < Node
|
1571
|
+
def compile(context)
|
1572
|
+
Ruby::MethodCall.new(
|
1573
|
+
:receiver => nil,
|
1574
|
+
:name => "_",
|
1575
|
+
:args => [Ruby::Literal.new(:value => text)],
|
1576
|
+
:block => nil,
|
1577
|
+
:parens => true
|
1578
|
+
)
|
1579
|
+
end
|
1580
|
+
|
1581
|
+
transfers_comments :compile
|
1582
|
+
|
1583
|
+
def never_nil?
|
1584
|
+
#locale can be only with constant strings
|
1585
|
+
return true
|
1586
|
+
end
|
1587
|
+
end
|
1588
|
+
|
1589
|
+
class Map < Node
|
1590
|
+
def compile(context)
|
1591
|
+
Ruby::Hash.new(:entries => children.map { |ch| ch.compile(context) })
|
1592
|
+
end
|
1593
|
+
|
1594
|
+
transfers_comments :compile
|
1595
|
+
|
1596
|
+
def empty?
|
1597
|
+
children.empty?
|
1598
|
+
end
|
1599
|
+
end
|
1600
|
+
|
1601
|
+
class MapElement < Node
|
1602
|
+
def compile(context)
|
1603
|
+
Ruby::HashEntry.new(
|
1604
|
+
:key => key.compile(context),
|
1605
|
+
:value => value.compile(context)
|
1606
|
+
)
|
1607
|
+
end
|
1608
|
+
|
1609
|
+
transfers_comments :compile
|
1610
|
+
end
|
1611
|
+
|
1612
|
+
class ModuleBlock < Node
|
1613
|
+
def compile(context)
|
1614
|
+
if name !~ /^[A-Z][a-zA-Z0-9_]*$/
|
1615
|
+
raise NotImplementedError,
|
1616
|
+
"Invalid module name: #{name.inspect}. Module names that are not Ruby class names are not supported."
|
1617
|
+
end
|
1618
|
+
|
1619
|
+
class_statements = []
|
1620
|
+
|
1621
|
+
context.inside self do |inner_context|
|
1622
|
+
class_statements += build_main_def(inner_context)
|
1623
|
+
class_statements += build_other_defs(inner_context)
|
1624
|
+
class_statements += build_publish_calls(inner_context)
|
1625
|
+
end
|
1626
|
+
|
1627
|
+
module_statements = [
|
1628
|
+
Ruby::Class.new(
|
1629
|
+
:name => "#{name}Class",
|
1630
|
+
:superclass => Ruby::Variable.new(:name => "Module"),
|
1631
|
+
:statements => Ruby::Statements.new(
|
1632
|
+
:statements => class_statements
|
1633
|
+
)
|
1634
|
+
),
|
1635
|
+
Ruby::Assignment.new(
|
1636
|
+
:lhs => Ruby::Variable.new(:name => name),
|
1637
|
+
:rhs => Ruby::MethodCall.new(
|
1638
|
+
:receiver => Ruby::Variable.new(:name => "#{name}Class"),
|
1639
|
+
:name => "new",
|
1640
|
+
:args => [],
|
1641
|
+
:block => nil,
|
1642
|
+
:parens => true
|
1643
|
+
),
|
1644
|
+
:comment_before => ""
|
1645
|
+
)
|
1646
|
+
]
|
1647
|
+
|
1648
|
+
if has_main_def?
|
1649
|
+
module_statements << Ruby::MethodCall.new(
|
1650
|
+
:receiver => Ruby::Variable.new(:name => name),
|
1651
|
+
:name => "main",
|
1652
|
+
:args => [],
|
1653
|
+
:block => nil,
|
1654
|
+
:parens => true
|
1655
|
+
)
|
1656
|
+
end
|
1657
|
+
|
1658
|
+
Ruby::Program.new(
|
1659
|
+
:statements => Ruby::Statements.new(
|
1660
|
+
:statements => [
|
1661
|
+
Ruby::MethodCall.new(
|
1662
|
+
:receiver => nil,
|
1663
|
+
:name => "require",
|
1664
|
+
:args => [Ruby::Literal.new(:value => "yast")],
|
1665
|
+
:block => nil,
|
1666
|
+
:parens => false
|
1667
|
+
),
|
1668
|
+
Ruby::Module.new(
|
1669
|
+
:name => "Yast",
|
1670
|
+
:statements => Ruby::Statements.new(
|
1671
|
+
:statements => module_statements
|
1672
|
+
),
|
1673
|
+
:comment_before => ""
|
1674
|
+
)
|
1675
|
+
]
|
1676
|
+
)
|
1677
|
+
)
|
1678
|
+
end
|
1679
|
+
|
1680
|
+
transfers_comments :compile
|
1681
|
+
|
1682
|
+
private
|
1683
|
+
|
1684
|
+
def fundef_statements
|
1685
|
+
statements.select { |s| s.is_a?(FunDef) }
|
1686
|
+
end
|
1687
|
+
|
1688
|
+
def other_statements
|
1689
|
+
statements - fundef_statements
|
1690
|
+
end
|
1691
|
+
|
1692
|
+
def constructor
|
1693
|
+
fundef_statements.find { |s| s.name == name }
|
1694
|
+
end
|
1695
|
+
|
1696
|
+
def has_main_def?
|
1697
|
+
!other_statements.empty? || constructor
|
1698
|
+
end
|
1699
|
+
|
1700
|
+
def build_main_def(context)
|
1701
|
+
if has_main_def?
|
1702
|
+
main_statements = compile_statements_with_whitespace(
|
1703
|
+
other_statements,
|
1704
|
+
context
|
1705
|
+
)
|
1706
|
+
|
1707
|
+
if constructor
|
1708
|
+
main_statements << Ruby::MethodCall.new(
|
1709
|
+
:receiver => nil,
|
1710
|
+
:name => name,
|
1711
|
+
:args => [],
|
1712
|
+
:block => nil,
|
1713
|
+
:parens => true
|
1714
|
+
)
|
1715
|
+
end
|
1716
|
+
|
1717
|
+
[
|
1718
|
+
Ruby::Def.new(
|
1719
|
+
:name => "main",
|
1720
|
+
:args => [],
|
1721
|
+
:statements => Ruby::Statements.new(
|
1722
|
+
:statements => main_statements
|
1723
|
+
)
|
1724
|
+
)
|
1725
|
+
]
|
1726
|
+
else
|
1727
|
+
[]
|
1728
|
+
end
|
1729
|
+
end
|
1730
|
+
|
1731
|
+
def build_other_defs(context)
|
1732
|
+
defs = compile_statements_with_whitespace(fundef_statements, context)
|
1733
|
+
|
1734
|
+
unless defs.empty?
|
1735
|
+
defs.first.ensure_separated if has_main_def?
|
1736
|
+
end
|
1737
|
+
|
1738
|
+
defs
|
1739
|
+
end
|
1740
|
+
|
1741
|
+
def build_publish_calls(context)
|
1742
|
+
exported_symbols = if context.options[:export_private]
|
1743
|
+
symbols
|
1744
|
+
else
|
1745
|
+
symbols.select(&:global)
|
1746
|
+
end
|
1747
|
+
|
1748
|
+
calls = exported_symbols.map { |s| s.compile_as_publish_call(context) }
|
1749
|
+
|
1750
|
+
unless calls.empty?
|
1751
|
+
if has_main_def? || !fundef_statements.empty?
|
1752
|
+
calls.first.ensure_separated
|
1753
|
+
end
|
1754
|
+
end
|
1755
|
+
|
1756
|
+
calls
|
1757
|
+
end
|
1758
|
+
end
|
1759
|
+
|
1760
|
+
class Repeat < Node
|
1761
|
+
def symbols
|
1762
|
+
[]
|
1763
|
+
end
|
1764
|
+
|
1765
|
+
def compile(context)
|
1766
|
+
Ruby::Until.new(
|
1767
|
+
:condition => self.until.compile(context),
|
1768
|
+
:body => Ruby::Begin.new(
|
1769
|
+
:statements => compile_statements_inside_block(self.do, context)
|
1770
|
+
)
|
1771
|
+
)
|
1772
|
+
end
|
1773
|
+
|
1774
|
+
transfers_comments :compile
|
1775
|
+
end
|
1776
|
+
|
1777
|
+
class Return < Node
|
1778
|
+
def compile(context)
|
1779
|
+
case context.innermost(DefBlock, FileBlock, UnspecBlock)
|
1780
|
+
when DefBlock, FileBlock
|
1781
|
+
Ruby::Return.new(
|
1782
|
+
:value => child ? child.compile_as_copy_if_needed(context) : nil
|
1783
|
+
)
|
1784
|
+
when UnspecBlock
|
1785
|
+
Ruby::Next.new(
|
1786
|
+
:value => child ? child.compile_as_copy_if_needed(context) : nil
|
1787
|
+
)
|
1788
|
+
else
|
1789
|
+
raise "Misplaced \"return\" statement."
|
1790
|
+
end
|
1791
|
+
end
|
1792
|
+
|
1793
|
+
def always_returns?
|
1794
|
+
true
|
1795
|
+
end
|
1796
|
+
|
1797
|
+
transfers_comments :compile
|
1798
|
+
end
|
1799
|
+
|
1800
|
+
class StmtBlock < Node
|
1801
|
+
def compile(context)
|
1802
|
+
context.inside self do |inner_context|
|
1803
|
+
Ruby::Statements.new(
|
1804
|
+
:statements => compile_statements_with_whitespace(
|
1805
|
+
statements,
|
1806
|
+
inner_context
|
1807
|
+
)
|
1808
|
+
)
|
1809
|
+
end
|
1810
|
+
end
|
1811
|
+
|
1812
|
+
def always_returns?
|
1813
|
+
statements.any? { |s| s.always_returns? }
|
1814
|
+
end
|
1815
|
+
|
1816
|
+
transfers_comments :compile
|
1817
|
+
end
|
1818
|
+
|
1819
|
+
class Switch < Node
|
1820
|
+
def compile(context)
|
1821
|
+
Ruby::Case.new(
|
1822
|
+
:expression => cond.compile(context),
|
1823
|
+
:whens => cases.map { |c| c.compile(context) },
|
1824
|
+
:else => default ? default.compile(context) : nil
|
1825
|
+
)
|
1826
|
+
end
|
1827
|
+
|
1828
|
+
def always_returns?
|
1829
|
+
if self.default
|
1830
|
+
cases.all? { |c| c.always_returns? } && default.always_returns?
|
1831
|
+
else
|
1832
|
+
# If there is no default clause present, execution can always
|
1833
|
+
# continue because the tested expression may not fit any of the
|
1834
|
+
# cases.
|
1835
|
+
false
|
1836
|
+
end
|
1837
|
+
end
|
1838
|
+
|
1839
|
+
transfers_comments :compile
|
1840
|
+
end
|
1841
|
+
|
1842
|
+
class Symbol < Node
|
1843
|
+
def needs_copy?
|
1844
|
+
type.needs_copy?
|
1845
|
+
end
|
1846
|
+
|
1847
|
+
def compile(context)
|
1848
|
+
RubyVar.for(nil, name, context, :in_arg)
|
1849
|
+
end
|
1850
|
+
|
1851
|
+
def compile_as_copy_arg_call(context)
|
1852
|
+
Ruby::Assignment.new(
|
1853
|
+
:lhs => RubyVar.for(nil, name, context, :in_code),
|
1854
|
+
:rhs => Ruby::MethodCall.new(
|
1855
|
+
:receiver => nil,
|
1856
|
+
:name => "deep_copy",
|
1857
|
+
:args => [RubyVar.for(nil, name, context, :in_code)],
|
1858
|
+
:block => nil,
|
1859
|
+
:parens => true
|
1860
|
+
)
|
1861
|
+
)
|
1862
|
+
end
|
1863
|
+
|
1864
|
+
def compile_as_publish_call(context)
|
1865
|
+
args = [
|
1866
|
+
Ruby::HashEntry.new(
|
1867
|
+
:key => Ruby::Literal.new(:value => category),
|
1868
|
+
:value => Ruby::Literal.new(:value => name.to_sym)
|
1869
|
+
),
|
1870
|
+
Ruby::HashEntry.new(
|
1871
|
+
:key => Ruby::Literal.new(:value => :type),
|
1872
|
+
:value => Ruby::Literal.new(:value => type.to_s)
|
1873
|
+
)
|
1874
|
+
]
|
1875
|
+
|
1876
|
+
unless global
|
1877
|
+
args << Ruby::HashEntry.new(
|
1878
|
+
:key => Ruby::Literal.new(:value => :private),
|
1879
|
+
:value => Ruby::Literal.new(:value => true)
|
1880
|
+
)
|
1881
|
+
end
|
1882
|
+
|
1883
|
+
Ruby::MethodCall.new(
|
1884
|
+
:receiver => nil,
|
1885
|
+
:name => "publish",
|
1886
|
+
:args => args,
|
1887
|
+
:block => nil,
|
1888
|
+
:parens => false
|
1889
|
+
)
|
1890
|
+
end
|
1891
|
+
|
1892
|
+
transfers_comments :compile
|
1893
|
+
end
|
1894
|
+
|
1895
|
+
class Textdomain < Node
|
1896
|
+
def compile(context)
|
1897
|
+
Ruby::MethodCall.new(
|
1898
|
+
:receiver => nil,
|
1899
|
+
:name => "textdomain",
|
1900
|
+
:args => [Ruby::Literal.new(:value => name)],
|
1901
|
+
:block => nil,
|
1902
|
+
:parens => false
|
1903
|
+
)
|
1904
|
+
end
|
1905
|
+
|
1906
|
+
transfers_comments :compile
|
1907
|
+
end
|
1908
|
+
|
1909
|
+
class Typedef < Node
|
1910
|
+
def compile(context)
|
1911
|
+
# Ignored because ycpc expands defined types in the XML, so we never
|
1912
|
+
# actually encounter them.
|
1913
|
+
end
|
1914
|
+
end
|
1915
|
+
|
1916
|
+
class UnspecBlock < Node
|
1917
|
+
def creates_local_scope?
|
1918
|
+
true
|
1919
|
+
end
|
1920
|
+
|
1921
|
+
def compile(context)
|
1922
|
+
context.inside self do |inner_context|
|
1923
|
+
Ruby::MethodCall.new(
|
1924
|
+
:receiver => nil,
|
1925
|
+
:name => "lambda",
|
1926
|
+
:args => [],
|
1927
|
+
:block => Ruby::Block.new(
|
1928
|
+
:args => [],
|
1929
|
+
:statements => Ruby::Statements.new(
|
1930
|
+
:statements => optimize_next(
|
1931
|
+
statements.map { |s| s.compile(inner_context) }
|
1932
|
+
)
|
1933
|
+
)
|
1934
|
+
),
|
1935
|
+
:parens => true
|
1936
|
+
)
|
1937
|
+
end
|
1938
|
+
end
|
1939
|
+
|
1940
|
+
def compile_as_block(context)
|
1941
|
+
context.inside self do |inner_context|
|
1942
|
+
Ruby::Block.new(
|
1943
|
+
:args => args.map { |a| a.compile(inner_context) },
|
1944
|
+
:statements => Ruby::Statements.new(
|
1945
|
+
:statements => optimize_next(
|
1946
|
+
statements.map { |s| s.compile(inner_context) }
|
1947
|
+
)
|
1948
|
+
)
|
1949
|
+
)
|
1950
|
+
end
|
1951
|
+
end
|
1952
|
+
|
1953
|
+
transfers_comments :compile, :compile_as_block
|
1954
|
+
end
|
1955
|
+
|
1956
|
+
class Variable < Node
|
1957
|
+
def needs_copy?
|
1958
|
+
case category
|
1959
|
+
when :variable, :reference
|
1960
|
+
type.needs_copy?
|
1961
|
+
when :function
|
1962
|
+
false
|
1963
|
+
else
|
1964
|
+
raise "Unknown variable category: #{category.inspect}."
|
1965
|
+
end
|
1966
|
+
end
|
1967
|
+
|
1968
|
+
def compile_as_copy_if_needed(context)
|
1969
|
+
node = compile(context)
|
1970
|
+
|
1971
|
+
if needs_copy?
|
1972
|
+
Ruby::MethodCall.new(
|
1973
|
+
:receiver => nil,
|
1974
|
+
:name => "deep_copy",
|
1975
|
+
:args => [node],
|
1976
|
+
:block => nil,
|
1977
|
+
:parens => true
|
1978
|
+
)
|
1979
|
+
else
|
1980
|
+
node
|
1981
|
+
end
|
1982
|
+
end
|
1983
|
+
|
1984
|
+
def compile(context)
|
1985
|
+
case category
|
1986
|
+
when :variable, :reference
|
1987
|
+
RubyVar.for(ns, name, context, :in_code)
|
1988
|
+
when :function
|
1989
|
+
getter = if !ns && context.locals.include?(name)
|
1990
|
+
RubyVar.for(nil, name, context, :in_code)
|
1991
|
+
else
|
1992
|
+
# In the XML, all global module function references are
|
1993
|
+
# qualified (e.g. "M::i"). This includes references to functions
|
1994
|
+
# defined in this module. The problem is that in generated Ruby
|
1995
|
+
# code, the module namespace may not exist yet (e.g. when the
|
1996
|
+
# function is refrenced at module toplvel in YCP), so we have to
|
1997
|
+
# omit it (which is OK, because then the |method| call will be
|
1998
|
+
# invoked on |self|, whish is always our module).
|
1999
|
+
real_ns = ns == context.module_name ? nil : ns
|
2000
|
+
|
2001
|
+
Ruby::MethodCall.new(
|
2002
|
+
:receiver => real_ns ? Ruby::Variable.new(:name => real_ns) : nil,
|
2003
|
+
:name => "method",
|
2004
|
+
:args => [
|
2005
|
+
Ruby::Literal.new(:value => name.to_sym)
|
2006
|
+
],
|
2007
|
+
:block => nil,
|
2008
|
+
:parens => true
|
2009
|
+
)
|
2010
|
+
end
|
2011
|
+
|
2012
|
+
Ruby::MethodCall.new(
|
2013
|
+
:receiver => nil,
|
2014
|
+
:name => "fun_ref",
|
2015
|
+
:args => [getter, Ruby::Literal.new(:value => type.to_s)],
|
2016
|
+
:block => nil,
|
2017
|
+
:parens => true
|
2018
|
+
)
|
2019
|
+
else
|
2020
|
+
raise "Unknown variable category: #{category.inspect}."
|
2021
|
+
end
|
2022
|
+
end
|
2023
|
+
|
2024
|
+
transfers_comments :compile
|
2025
|
+
end
|
2026
|
+
|
2027
|
+
class While < Node
|
2028
|
+
def symbols
|
2029
|
+
[]
|
2030
|
+
end
|
2031
|
+
|
2032
|
+
def compile(context)
|
2033
|
+
Ruby::While.new(
|
2034
|
+
:condition => cond.compile(context),
|
2035
|
+
:body => compile_statements_inside_block(self.do, context)
|
2036
|
+
)
|
2037
|
+
end
|
2038
|
+
|
2039
|
+
transfers_comments :compile
|
2040
|
+
end
|
2041
|
+
|
2042
|
+
class YCPCode < Node
|
2043
|
+
def creates_local_scope?
|
2044
|
+
true
|
2045
|
+
end
|
2046
|
+
|
2047
|
+
def compile(context)
|
2048
|
+
Ruby::MethodCall.new(
|
2049
|
+
:receiver => nil,
|
2050
|
+
:name => "lambda",
|
2051
|
+
:args => [],
|
2052
|
+
:block => Ruby::Block.new(
|
2053
|
+
:args => [],
|
2054
|
+
:statements => child.compile(context)
|
2055
|
+
),
|
2056
|
+
:parens => true
|
2057
|
+
)
|
2058
|
+
end
|
2059
|
+
|
2060
|
+
def compile_as_block(context)
|
2061
|
+
context.inside self do |inner_context|
|
2062
|
+
Ruby::Block.new(
|
2063
|
+
:args => args.map { |a| a.compile(inner_context) },
|
2064
|
+
:statements => child.compile(inner_context)
|
2065
|
+
)
|
2066
|
+
end
|
2067
|
+
end
|
2068
|
+
|
2069
|
+
transfers_comments :compile, :compile_as_block
|
2070
|
+
end
|
2071
|
+
|
2072
|
+
class YEBinary < Node
|
2073
|
+
OPS_TO_OPS = {
|
2074
|
+
"&&" => "&&",
|
2075
|
+
"||" => "||"
|
2076
|
+
}
|
2077
|
+
|
2078
|
+
OPS_TO_OPS_OPTIONAL = {
|
2079
|
+
"+" => "+",
|
2080
|
+
"-" => "-",
|
2081
|
+
"*" => "*",
|
2082
|
+
"/" => "/",
|
2083
|
+
"%" => "%",
|
2084
|
+
"&" => "&",
|
2085
|
+
"|" => "|",
|
2086
|
+
"^" => "^",
|
2087
|
+
"<<" => "<<",
|
2088
|
+
">>" => ">>",
|
2089
|
+
}
|
2090
|
+
|
2091
|
+
OPS_TO_METHODS = {
|
2092
|
+
"+" => "add",
|
2093
|
+
"-" => "subtract",
|
2094
|
+
"*" => "multiply",
|
2095
|
+
"/" => "divide",
|
2096
|
+
"%" => "modulo",
|
2097
|
+
"&" => "bitwise_and",
|
2098
|
+
"|" => "bitwise_or",
|
2099
|
+
"^" => "bitwise_xor",
|
2100
|
+
"<<" => "shift_left",
|
2101
|
+
">>" => "shift_right"
|
2102
|
+
}
|
2103
|
+
|
2104
|
+
def compile(context)
|
2105
|
+
if OPS_TO_OPS[name]
|
2106
|
+
Ruby::BinaryOperator.new(
|
2107
|
+
:op => OPS_TO_OPS[name],
|
2108
|
+
:lhs => lhs.compile(context),
|
2109
|
+
:rhs => rhs.compile(context)
|
2110
|
+
)
|
2111
|
+
elsif OPS_TO_METHODS[name]
|
2112
|
+
if never_nil?
|
2113
|
+
Ruby::BinaryOperator.new(
|
2114
|
+
:op => OPS_TO_OPS_OPTIONAL[name],
|
2115
|
+
:lhs => lhs.compile(context),
|
2116
|
+
:rhs => rhs.compile(context)
|
2117
|
+
)
|
2118
|
+
else
|
2119
|
+
Ruby::MethodCall.new(
|
2120
|
+
:receiver => Ruby::Variable.new(:name => "Ops"),
|
2121
|
+
:name => OPS_TO_METHODS[name],
|
2122
|
+
:args => [lhs.compile(context), rhs.compile(context)],
|
2123
|
+
:block => nil,
|
2124
|
+
:parens => true
|
2125
|
+
)
|
2126
|
+
end
|
2127
|
+
else
|
2128
|
+
raise "Unknown binary operator: #{name.inspect}."
|
2129
|
+
end
|
2130
|
+
end
|
2131
|
+
|
2132
|
+
transfers_comments :compile
|
2133
|
+
|
2134
|
+
def never_nil?
|
2135
|
+
return lhs.never_nil? && rhs.never_nil?
|
2136
|
+
end
|
2137
|
+
end
|
2138
|
+
|
2139
|
+
# Forward declaration needed for |YEBracket::LAZY_DEFULT_CLASSES|.
|
2140
|
+
class YETerm < Node
|
2141
|
+
end
|
2142
|
+
|
2143
|
+
class YEBracket < Node
|
2144
|
+
def compile(context)
|
2145
|
+
args, block = build_args_and_block(context)
|
2146
|
+
|
2147
|
+
Ruby::MethodCall.new(
|
2148
|
+
:receiver => Ruby::Variable.new(:name => "Ops"),
|
2149
|
+
:name => "get",
|
2150
|
+
:args => args,
|
2151
|
+
:block => block,
|
2152
|
+
:parens => true
|
2153
|
+
)
|
2154
|
+
end
|
2155
|
+
|
2156
|
+
def compile_as_shortcut(type, context)
|
2157
|
+
args, block = build_args_and_block(context)
|
2158
|
+
|
2159
|
+
Ruby::MethodCall.new(
|
2160
|
+
:receiver => Ruby::Variable.new(:name => "Ops"),
|
2161
|
+
:name => "get_#{type}",
|
2162
|
+
:args => args,
|
2163
|
+
:block => block,
|
2164
|
+
:parens => true
|
2165
|
+
)
|
2166
|
+
end
|
2167
|
+
|
2168
|
+
transfers_comments :compile
|
2169
|
+
|
2170
|
+
private
|
2171
|
+
|
2172
|
+
def evaluate_default_lazily?
|
2173
|
+
is_call = default.is_a?(Call)
|
2174
|
+
is_non_empty_list = default.is_a?(List) && !default.empty?
|
2175
|
+
is_non_empty_map = default.is_a?(Map) && !default.empty?
|
2176
|
+
is_non_empty_term = default.is_a?(YETerm) && !default.empty?
|
2177
|
+
|
2178
|
+
is_call || is_non_empty_list || is_non_empty_map || is_non_empty_term
|
2179
|
+
end
|
2180
|
+
|
2181
|
+
def build_index(context)
|
2182
|
+
if index.children.size == 1
|
2183
|
+
index.children.first.compile(context)
|
2184
|
+
else
|
2185
|
+
index.compile(context)
|
2186
|
+
end
|
2187
|
+
end
|
2188
|
+
|
2189
|
+
def build_args_and_block(context)
|
2190
|
+
# In expressions like |m["foo"]:f()|, the |f| function is called only
|
2191
|
+
# when the value is missing. In other words, the default is evaluated
|
2192
|
+
# lazily. We need to emulate this laziness for calls and all
|
2193
|
+
# expressions that can contain them.
|
2194
|
+
if evaluate_default_lazily?
|
2195
|
+
args = [value.compile(context), build_index(context)]
|
2196
|
+
block = Ruby::Block.new(
|
2197
|
+
:args => [],
|
2198
|
+
:statements => default.compile(context)
|
2199
|
+
)
|
2200
|
+
else
|
2201
|
+
args = [
|
2202
|
+
value.compile(context),
|
2203
|
+
build_index(context),
|
2204
|
+
]
|
2205
|
+
|
2206
|
+
if !(default.is_a?(Const) && default.type == :void)
|
2207
|
+
args << default.compile(context)
|
2208
|
+
end
|
2209
|
+
|
2210
|
+
block = nil
|
2211
|
+
end
|
2212
|
+
|
2213
|
+
[args, block]
|
2214
|
+
end
|
2215
|
+
end
|
2216
|
+
|
2217
|
+
class YEIs < Node
|
2218
|
+
KNOWN_SHORTCUTS = [
|
2219
|
+
'any',
|
2220
|
+
'boolean',
|
2221
|
+
'byteblock',
|
2222
|
+
'float',
|
2223
|
+
'integer',
|
2224
|
+
'list',
|
2225
|
+
'locale',
|
2226
|
+
'map',
|
2227
|
+
'path',
|
2228
|
+
'string',
|
2229
|
+
'symbol',
|
2230
|
+
'term',
|
2231
|
+
'void',
|
2232
|
+
]
|
2233
|
+
|
2234
|
+
def compile(context)
|
2235
|
+
if KNOWN_SHORTCUTS.include?(type.to_s)
|
2236
|
+
Ruby::MethodCall.new(
|
2237
|
+
:receiver => Ruby::Variable.new(:name => "Ops"),
|
2238
|
+
:name => "is_#{type}?",
|
2239
|
+
:args => [
|
2240
|
+
child.compile(context)
|
2241
|
+
],
|
2242
|
+
:block => nil,
|
2243
|
+
:parens => true
|
2244
|
+
)
|
2245
|
+
else
|
2246
|
+
Ruby::MethodCall.new(
|
2247
|
+
:receiver => Ruby::Variable.new(:name => "Ops"),
|
2248
|
+
:name => "is",
|
2249
|
+
:args => [
|
2250
|
+
child.compile(context),
|
2251
|
+
Ruby::Literal.new(:value => type.to_s)
|
2252
|
+
],
|
2253
|
+
:block => nil,
|
2254
|
+
:parens => true
|
2255
|
+
)
|
2256
|
+
end
|
2257
|
+
end
|
2258
|
+
|
2259
|
+
transfers_comments :compile
|
2260
|
+
end
|
2261
|
+
|
2262
|
+
class YEPropagate < Node
|
2263
|
+
# Needs to be in sync with |Yast::Ops::SHORTCUT_TYPES| in Ruby bindings.
|
2264
|
+
TYPES_WITH_SHORTCUT_CONVERSION = [
|
2265
|
+
"boolean",
|
2266
|
+
"float",
|
2267
|
+
"integer",
|
2268
|
+
"list",
|
2269
|
+
"locale",
|
2270
|
+
"map",
|
2271
|
+
"path",
|
2272
|
+
"string",
|
2273
|
+
"symbol",
|
2274
|
+
"term",
|
2275
|
+
]
|
2276
|
+
|
2277
|
+
def compile(context)
|
2278
|
+
if from.no_const != to.no_const
|
2279
|
+
if compile_as_shortcut?
|
2280
|
+
if child.is_a?(YEBracket)
|
2281
|
+
child.compile_as_shortcut(to.no_const, context)
|
2282
|
+
else
|
2283
|
+
Ruby::MethodCall.new(
|
2284
|
+
:receiver => Ruby::Variable.new(:name => "Convert"),
|
2285
|
+
:name => "to_#{to.no_const}",
|
2286
|
+
:args => [child.compile(context)],
|
2287
|
+
:block => nil,
|
2288
|
+
:parens => true
|
2289
|
+
)
|
2290
|
+
end
|
2291
|
+
else
|
2292
|
+
Ruby::MethodCall.new(
|
2293
|
+
:receiver => Ruby::Variable.new(:name => "Convert"),
|
2294
|
+
:name => "convert",
|
2295
|
+
:args => [
|
2296
|
+
child.compile(context),
|
2297
|
+
Ruby::HashEntry.new(
|
2298
|
+
:key => Ruby::Literal.new(:value => :from),
|
2299
|
+
:value => Ruby::Literal.new(:value => from.no_const.to_s)
|
2300
|
+
),
|
2301
|
+
Ruby::HashEntry.new(
|
2302
|
+
:key => Ruby::Literal.new(:value => :to),
|
2303
|
+
:value => Ruby::Literal.new(:value => to.no_const.to_s)
|
2304
|
+
)
|
2305
|
+
],
|
2306
|
+
:block => nil,
|
2307
|
+
:parens => true
|
2308
|
+
)
|
2309
|
+
end
|
2310
|
+
else
|
2311
|
+
child.compile(context)
|
2312
|
+
end
|
2313
|
+
end
|
2314
|
+
|
2315
|
+
transfers_comments :compile
|
2316
|
+
|
2317
|
+
private
|
2318
|
+
|
2319
|
+
def compile_as_shortcut?
|
2320
|
+
shortcut_exists = TYPES_WITH_SHORTCUT_CONVERSION.include?(to.no_const.to_s)
|
2321
|
+
from_any = from.no_const.to_s == "any"
|
2322
|
+
|
2323
|
+
from_any && shortcut_exists
|
2324
|
+
end
|
2325
|
+
end
|
2326
|
+
|
2327
|
+
class YEReference < Node
|
2328
|
+
def compile(context)
|
2329
|
+
child.compile_as_ref(context)
|
2330
|
+
end
|
2331
|
+
|
2332
|
+
def compile_as_setter(context)
|
2333
|
+
Ruby::Assignment.new(
|
2334
|
+
:lhs => compile(context),
|
2335
|
+
:rhs => Ruby::MethodCall.new(
|
2336
|
+
:receiver => nil,
|
2337
|
+
:name => "arg_ref",
|
2338
|
+
:args => [child.compile(context)],
|
2339
|
+
:block => nil,
|
2340
|
+
:parens => true
|
2341
|
+
)
|
2342
|
+
)
|
2343
|
+
end
|
2344
|
+
|
2345
|
+
def compile_as_getter(context)
|
2346
|
+
Ruby::Assignment.new(
|
2347
|
+
:lhs => child.compile(context),
|
2348
|
+
:rhs => Ruby::MethodCall.new(
|
2349
|
+
:receiver => compile(context),
|
2350
|
+
:name => "value",
|
2351
|
+
:args => [],
|
2352
|
+
:block => nil,
|
2353
|
+
:parens => true
|
2354
|
+
)
|
2355
|
+
)
|
2356
|
+
end
|
2357
|
+
|
2358
|
+
transfers_comments :compile, :compile_as_setter, :compile_as_getter
|
2359
|
+
end
|
2360
|
+
|
2361
|
+
class YEReturn < Node
|
2362
|
+
def creates_local_scope?
|
2363
|
+
true
|
2364
|
+
end
|
2365
|
+
|
2366
|
+
def compile(context)
|
2367
|
+
Ruby::MethodCall.new(
|
2368
|
+
:receiver => nil,
|
2369
|
+
:name => "lambda",
|
2370
|
+
:args => [],
|
2371
|
+
:block => Ruby::Block.new(
|
2372
|
+
:args => [],
|
2373
|
+
:statements => child.compile(context)
|
2374
|
+
),
|
2375
|
+
:parens => true
|
2376
|
+
)
|
2377
|
+
end
|
2378
|
+
|
2379
|
+
def compile_as_block(context)
|
2380
|
+
context.inside self do |inner_context|
|
2381
|
+
Ruby::Block.new(
|
2382
|
+
:args => args.map { |a| a.compile(inner_context) },
|
2383
|
+
:statements => child.compile(inner_context)
|
2384
|
+
)
|
2385
|
+
end
|
2386
|
+
end
|
2387
|
+
|
2388
|
+
transfers_comments :compile, :compile_as_block
|
2389
|
+
end
|
2390
|
+
|
2391
|
+
class YETerm < Node
|
2392
|
+
UI_TERMS = [
|
2393
|
+
:BarGraph,
|
2394
|
+
:BusyIndicator,
|
2395
|
+
:Bottom,
|
2396
|
+
:ButtonBox,
|
2397
|
+
:Cell,
|
2398
|
+
:Center,
|
2399
|
+
:CheckBox,
|
2400
|
+
:CheckBoxFrame,
|
2401
|
+
:ColoredLabel,
|
2402
|
+
:ComboBox,
|
2403
|
+
:DateField,
|
2404
|
+
:DownloadProgress,
|
2405
|
+
:DumbTab,
|
2406
|
+
:Dummy,
|
2407
|
+
:DummySpecialWidget,
|
2408
|
+
:Empty,
|
2409
|
+
:Frame,
|
2410
|
+
:HBox,
|
2411
|
+
:HCenter,
|
2412
|
+
:HMultiProgressMeter,
|
2413
|
+
:HSpacing,
|
2414
|
+
:HSquash,
|
2415
|
+
:HStretch,
|
2416
|
+
:HVCenter,
|
2417
|
+
:HVSquash,
|
2418
|
+
:HVStretch,
|
2419
|
+
:HWeight,
|
2420
|
+
:Heading,
|
2421
|
+
:IconButton,
|
2422
|
+
:Image,
|
2423
|
+
:InputField,
|
2424
|
+
:IntField,
|
2425
|
+
:Label,
|
2426
|
+
:Left,
|
2427
|
+
:LogView,
|
2428
|
+
:MarginBox,
|
2429
|
+
:MenuButton,
|
2430
|
+
:MinHeight,
|
2431
|
+
:MinSize,
|
2432
|
+
:MinWidth,
|
2433
|
+
:MultiLineEdit,
|
2434
|
+
:MultiSelectionBox,
|
2435
|
+
:PackageSelector,
|
2436
|
+
:PatternSelector,
|
2437
|
+
:PartitionSplitter,
|
2438
|
+
:Password,
|
2439
|
+
:PkgSpecial,
|
2440
|
+
:ProgressBar,
|
2441
|
+
:PushButton,
|
2442
|
+
:RadioButton,
|
2443
|
+
:RadioButtonGroup,
|
2444
|
+
:ReplacePoint,
|
2445
|
+
:RichText,
|
2446
|
+
:Right,
|
2447
|
+
:SelectionBox,
|
2448
|
+
:Slider,
|
2449
|
+
:Table,
|
2450
|
+
:TextEntry,
|
2451
|
+
:TimeField,
|
2452
|
+
:TimezoneSelector,
|
2453
|
+
:Top,
|
2454
|
+
:Tree,
|
2455
|
+
:VBox,
|
2456
|
+
:VCenter,
|
2457
|
+
:VMultiProgressMeter,
|
2458
|
+
:VSpacing,
|
2459
|
+
:VSquash,
|
2460
|
+
:VStretch,
|
2461
|
+
:VWeight,
|
2462
|
+
:Wizard,
|
2463
|
+
# special ones that will have upper case shortcut, but in term is lowercase
|
2464
|
+
:id,
|
2465
|
+
:item,
|
2466
|
+
:header,
|
2467
|
+
:opt,
|
2468
|
+
]
|
2469
|
+
|
2470
|
+
def compile(context)
|
2471
|
+
children_compiled = children.map { |ch| ch.compile(context) }
|
2472
|
+
|
2473
|
+
method_name = name.dup
|
2474
|
+
method_name[0] = method_name[0].upcase
|
2475
|
+
if UI_TERMS.include?(name.to_sym) && !context.symbols.include?(method_name)
|
2476
|
+
Ruby::MethodCall.new(
|
2477
|
+
:receiver => nil,
|
2478
|
+
:name => method_name,
|
2479
|
+
:args => children_compiled,
|
2480
|
+
:block => nil,
|
2481
|
+
:parens => true
|
2482
|
+
)
|
2483
|
+
else
|
2484
|
+
name_compiled = Ruby::Literal.new(:value => name.to_sym)
|
2485
|
+
|
2486
|
+
Ruby::MethodCall.new(
|
2487
|
+
:receiver => nil,
|
2488
|
+
:name => "term",
|
2489
|
+
:args => [name_compiled] + children_compiled,
|
2490
|
+
:block => nil,
|
2491
|
+
:parens => true
|
2492
|
+
)
|
2493
|
+
end
|
2494
|
+
end
|
2495
|
+
|
2496
|
+
transfers_comments :compile
|
2497
|
+
|
2498
|
+
def empty?
|
2499
|
+
children.empty?
|
2500
|
+
end
|
2501
|
+
end
|
2502
|
+
|
2503
|
+
class YETriple < Node
|
2504
|
+
def compile(context)
|
2505
|
+
Ruby::TernaryOperator.new(
|
2506
|
+
:condition => cond.compile(context),
|
2507
|
+
:then => self.true.compile(context),
|
2508
|
+
:else => self.false.compile(context)
|
2509
|
+
)
|
2510
|
+
end
|
2511
|
+
|
2512
|
+
transfers_comments :compile
|
2513
|
+
|
2514
|
+
def never_nil?
|
2515
|
+
return self.true.never_nil? && self.false.never_nil?
|
2516
|
+
end
|
2517
|
+
end
|
2518
|
+
|
2519
|
+
class YEUnary < Node
|
2520
|
+
OPS_TO_OPS = {
|
2521
|
+
"!" => "!"
|
2522
|
+
}
|
2523
|
+
|
2524
|
+
OPS_TO_OPS_OPTIONAL = {
|
2525
|
+
"-" => "-",
|
2526
|
+
"~" => "~",
|
2527
|
+
}
|
2528
|
+
|
2529
|
+
OPS_TO_METHODS = {
|
2530
|
+
"-" => "unary_minus",
|
2531
|
+
"~" => "bitwise_not",
|
2532
|
+
}
|
2533
|
+
|
2534
|
+
def compile(context)
|
2535
|
+
if OPS_TO_OPS[name]
|
2536
|
+
Ruby::UnaryOperator.new(
|
2537
|
+
:op => OPS_TO_OPS[name],
|
2538
|
+
:expression => child.compile(context)
|
2539
|
+
)
|
2540
|
+
elsif OPS_TO_METHODS[name]
|
2541
|
+
if never_nil?
|
2542
|
+
Ruby::UnaryOperator.new(
|
2543
|
+
:op => OPS_TO_OPS_OPTIONAL[name],
|
2544
|
+
:expression => child.compile(context)
|
2545
|
+
)
|
2546
|
+
else
|
2547
|
+
Ruby::MethodCall.new(
|
2548
|
+
:receiver => Ruby::Variable.new(:name => "Ops"),
|
2549
|
+
:name => OPS_TO_METHODS[name],
|
2550
|
+
:args => [child.compile(context)],
|
2551
|
+
:block => nil,
|
2552
|
+
:parens => true
|
2553
|
+
)
|
2554
|
+
end
|
2555
|
+
else
|
2556
|
+
raise "Unknown unary operator: #{name.inspect}."
|
2557
|
+
end
|
2558
|
+
end
|
2559
|
+
|
2560
|
+
transfers_comments :compile
|
2561
|
+
|
2562
|
+
def never_nil?
|
2563
|
+
return child.never_nil?
|
2564
|
+
end
|
2565
|
+
end
|
2566
|
+
end
|
2567
|
+
end
|
2568
|
+
end
|