Module: Maccro::CodeUtil

Defined in:
lib/maccro/code_util.rb

Class Method Summary collapse

Class Method Details

.code_position_to_index(source, lineno, column) ⇒ Object



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/maccro/code_util.rb', line 3

def self.code_position_to_index(source, lineno, column)
  source_lines = source.lines # including newline at the end of line
  if source_lines.size < lineno
    raise "too few lines for specified position: lineno:#{lineno}, column:#{column}"
  end
  counter = 1
  index = 0
  while counter < lineno
    index += source_lines.shift.size
    counter += 1
  end
  if source_lines.empty?
    raise "too few lines for specified position: lineno:#{lineno}, column:#{column}"
  end
  # column is 0 origin
  if source_lines.first.size < 1
    raise "empty line at the end of source"
  end
  if source_lines.first.size < column
    raise "too few chars in the line for specified position: lineno:#{lineno}, column:#{column}"
  end
  return index + column
end

.code_range_to_code(source, code_range) ⇒ Object



33
34
35
# File 'lib/maccro/code_util.rb', line 33

def self.code_range_to_code(source, code_range)
  source[code_range_to_range(source, code_range)]
end

.code_range_to_range(source, code_range) ⇒ Object



27
28
29
30
31
# File 'lib/maccro/code_util.rb', line 27

def self.code_range_to_range(source, code_range)
  begin_index = code_position_to_index(source, code_range.first_lineno, code_range.first_column)
  end_index = code_position_to_index(source, code_range.last_lineno, code_range.last_column)
  Range.new(begin_index, end_index, true) # exclude end char
end

.convert_scope_to_lambda(scope_source) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/maccro/code_util.rb', line 69

def self.convert_scope_to_lambda(scope_source)
  raise "Scope source must start with '{'" unless scope_source.start_with?('{')
  raise "Scope source must end with '}'" unless scope_source.end_with?('}')

  if m = scope_source.match(/^\{\s*\|(.*)\|/o)
    matched_source = m[0]
    args_source = m[1]
    return "->(#{args_source})" + scope_source.sub(matched_source, '{')
  end

  "->" + scope_source
end

.dig_method_node(node, def_type, method_name_index, method_name, lineno, column) ⇒ Object



150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/maccro/code_util.rb', line 150

def self.dig_method_node(node, def_type, method_name_index, method_name, lineno, column)
  return nil unless node.is_a?(RubyVM::AbstractSyntaxTree::Node)
  if node.type == def_type && node.children[method_name_index] == method_name && node.first_lineno == lineno && node.first_column == column
    return node
  elsif node.respond_to?(:children)
    node.children.each do |n|
      r = dig_method_node(n, def_type, method_name_index, method_name, lineno, column)
      return r if r
    end
  end
  nil
end

.dig_proc_node(node, lineno, column) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/maccro/code_util.rb', line 101

def self.dig_proc_node(node, lineno, column)
  return nil unless node.is_a?(RubyVM::AbstractSyntaxTree::Node)
  is_target_scope = ->(n){ n.type == :SCOPE && n.first_lineno == lineno && n.first_column == column }

  case node.type
  when :LAMBDA # ->(){ }
    if is_target_scope.call(node.children[0])
      return node
    end
  when :ITER # method call with block (iterator?)
    # lambda{}, proc{}
    if node.children[0].type == :FCALL \
       && (node.children[0].children[0] == :lambda || node.children[0].children[0] == :proc) \
       && is_target_scope.call(node.children[1])
      return node
    # Kernel.lambda, Kernel.proc{}, Proc.new{}
    elsif node.children[0].type == :CALL \
          && node.children[0].children[0].type == :CONST \
          && (node.children[0].children[0].children[0] == :Kernel && (node.children[0].children[1] == :lambda || node.children[0].children[1] == :proc) \
              || node.children[0].children[0].children[0] == :Proc && node.children[0].children[1] == :new ) \
          && is_target_scope.call(node.children[1])
      return node
    end
  when :SCOPE # for block parameters
    if is_target_scope.call(node)
      return node
    end
  end

  if node.respond_to?(:children)
    node.children.each do |n|
      r = dig_proc_node(n, lineno, column)
      return r if r
    end
  end

  nil
end

.extend_tree_with_wrapper(tree) ⇒ Object



61
62
63
64
65
66
67
# File 'lib/maccro/code_util.rb', line 61

def self.extend_tree_with_wrapper(tree)
  return unless tree.is_a?(RubyVM::AbstractSyntaxTree::Node)
  tree.extend Maccro::DSL::ASTNodeWrapper unless tree.is_a?(Maccro::DSL::ASTNodeWrapper)
  tree.children.each do |c|
    extend_tree_with_wrapper(c)
  end
end

.get_method_node(node, method_name, lineno, column, singleton_method: false) ⇒ Object



140
141
142
143
144
145
146
147
148
# File 'lib/maccro/code_util.rb', line 140

def self.get_method_node(node, method_name, lineno, column, singleton_method: false)
  if singleton_method
    # TODO: consider receiver filter
    # 0: (SELF@57:6-57:10)
    dig_method_node(node, :DEFS, 1, method_name, lineno, column)
  else
    dig_method_node(node, :DEFN, 0, method_name, lineno, column)
  end
end

.get_proc_node(node, lineno, column) ⇒ Object



96
97
98
99
# File 'lib/maccro/code_util.rb', line 96

def self.get_proc_node(node, lineno, column)
  return nil unless node.type == :SCOPE
  dig_proc_node(node, lineno, column)
end

.get_source_path(block) ⇒ Object



82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/maccro/code_util.rb', line 82

def self.get_source_path(block)
  iseq = CodeUtil.proc_to_iseq(block)
  if !iseq
    raise "Native methods can't be redefined"
  end
  path = iseq.absolute_path
  if !path # STDIN or -e
    raise "Methods from stdin or -e can't be redefined"
  end
  source = File.read(path)

  return source, path
end

.parse_to_ast(code) ⇒ Object



45
46
47
48
49
# File 'lib/maccro/code_util.rb', line 45

def self.parse_to_ast(code)
  suppress_warning do
    RubyVM::AbstractSyntaxTree.parse(code)
  end
end

.proc_to_ast(block) ⇒ Object



51
52
53
54
55
# File 'lib/maccro/code_util.rb', line 51

def self.proc_to_ast(block)
  suppress_warning do
    RubyVM::AbstractSyntaxTree.of(block)
  end
end

.proc_to_iseq(block) ⇒ Object



57
58
59
# File 'lib/maccro/code_util.rb', line 57

def self.proc_to_iseq(block)
  RubyVM::InstructionSequence.of(block)
end

.suppress_warningObject



37
38
39
40
41
42
43
# File 'lib/maccro/code_util.rb', line 37

def self.suppress_warning
  v = $VERBOSE
  $VERBOSE = nil
  yield
ensure
  $VERBOSE = v
end