Module: Y2R::AST::YCP::RubyVar

Defined in:
lib/y2r/ast/ycp.rb

Overview

Contains utility functions related to Ruby variables.

Constant Summary collapse

RUBY_KEYWORDS =

Taken from Ruby’s parse.y (for 1.9.3).

[
  "BEGIN",
  "END",
  "__ENCODING__",
  "__FILE__",
  "__LINE__",
  "alias",
  "and",
  "begin",
  "break",
  "case",
  "class",
  "def",
  "defined",
  "do",
  "else",
  "elsif",
  "end",
  "ensure",
  "false",
  "for",
  "if",
  "in",
  "module",
  "next",
  "nil",
  "not",
  "or",
  "redo",
  "rescue",
  "retry",
  "return",
  "self",
  "super",
  "then",
  "true",
  "undef",
  "unless",
  "until",
  "when",
  "while",
  "yield"
]

Class Method Summary collapse

Class Method Details

.escape_local(name) ⇒ Object

Escapes a YCP variable name so that it is a valid Ruby local variable name.

The escaping is constructed so that it can’t create any collision between names. More precisely, for any distinct strings passed to this function the results will be also distinct.



565
566
567
# File 'lib/y2r/ast/ycp.rb', line 565

def escape_local(name)
  name.sub(/^(#{RUBY_KEYWORDS.join("|")}|[A-Z_].*)$/) { |s| "_#{s}" }
end

.for(ns, name, context, mode) ⇒ Object

Builds a Ruby AST node for a variable with given name in given context, doing all necessary escaping, de-aliasing, etc.



571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
# File 'lib/y2r/ast/ycp.rb', line 571

def for(ns, name, context, mode)
  # In the XML, all global module variable references are qualified
  # (e.g. "M::i"). This includes references to variables defined in
  # this module. All other variable references are unqualified (e.g
  # "i").
  if ns
    if ns == context.module_name
      Ruby::Variable.new(:name => "@#{name}")
    else
      Ruby::MethodCall.new(
        :receiver => Ruby::Variable.new(:name => ns),
        :name     => name,
        :args     => [],
        :block    => nil,
        :parens   => true
      )
    end
  else
    is_local = context.locals.include?(name)
    variables = if is_local
      context.locals
    else
      context.globals
    end

    # If there already is a variable with given name (coming from some
    # parent scope), suffix the variable name with "2". If there are two
    # such variables, suffix the name with "3". And so on.
    #
    # The loop is needed because we need to do the same check and maybe
    # additional round(s) of suffixing also for suffixed variable names to
    # prevent conflicts.
    suffixed_name = name
    begin
      count = variables.select { |v| v == suffixed_name }.size
      suffixed_name = suffixed_name + count.to_s if count > 1
    end while count > 1

    variable_name = if is_local
      RubyVar.escape_local(suffixed_name)
    else
      "@#{suffixed_name}"
    end

    variable = Ruby::Variable.new(:name => variable_name)

    case mode
      when :in_code
        symbol = context.symbol_for(name)
        # The "symbol &&" part is needed only because of tests. The symbol
        # should be always present in real-world situations.
        if symbol && symbol.category == :reference
          Ruby::MethodCall.new(
            :receiver => variable,
            :name     => "value",
            :args     => [],
            :block    => nil,
            :parens   => true
          )
        else
          variable
        end

      when :in_arg
        variable

      else
        raise "Unknown mode: #{mode.inspect}."
    end
  end
end