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.
@@ -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 -> ![ALT text](path)
416
+ # uses markdown syntax
417
+ segment.gsub! /^(#\s+)@screenshot\s+(\S+)/,
418
+ "\\1![\\2](../../\\2)"
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