Class: MilkTea::DebugMap

Inherits:
Object
  • Object
show all
Defined in:
lib/milk_tea/tooling/debug_map.rb

Defined Under Namespace

Classes: Entry, Function

Constant Summary collapse

VERSION =
1

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(binary_path:, program_source_path:, functions:) ⇒ DebugMap

Returns a new instance of DebugMap.



39
40
41
42
43
44
45
46
# File 'lib/milk_tea/tooling/debug_map.rb', line 39

def initialize(binary_path:, program_source_path:, functions:)
  @binary_path = binary_path ? File.expand_path(binary_path) : nil
  @program_source_path = program_source_path ? File.expand_path(program_source_path) : nil
  @functions = functions
  @functions_by_c_name = functions.each_with_object({}) do |function, memo|
    memo[function.linkage_name] ||= function
  end
end

Instance Attribute Details

#binary_pathObject (readonly)

Returns the value of attribute binary_path.



37
38
39
# File 'lib/milk_tea/tooling/debug_map.rb', line 37

def binary_path
  @binary_path
end

#functionsObject (readonly)

Returns the value of attribute functions.



37
38
39
# File 'lib/milk_tea/tooling/debug_map.rb', line 37

def functions
  @functions
end

#program_source_pathObject (readonly)

Returns the value of attribute program_source_path.



37
38
39
# File 'lib/milk_tea/tooling/debug_map.rb', line 37

def program_source_path
  @program_source_path
end

Class Method Details

.absolute_path_string?(path) ⇒ Boolean

Returns:

  • (Boolean)


163
164
165
# File 'lib/milk_tea/tooling/debug_map.rb', line 163

def self.absolute_path_string?(path)
  path.start_with?("/") || path.start_with?("\\\\") || path.match?(/\A[A-Za-z]:[\\\/]/)
end

.from_ir(ir_program, binary_path:) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/milk_tea/tooling/debug_map.rb', line 83

def self.from_ir(ir_program, binary_path:)
  functions = ir_program.functions.map do |function|
    source_path = first_statement_source_path(function.body) || ir_program.source_path
    Function.new(
      name: function.name.to_s,
      linkage_name: function.linkage_name.to_s,
      source_path: source_path ? File.expand_path(source_path) : nil,
      line: first_statement_line(function.body),
      params: function.params.map { |param| Entry.new(name: param.name.to_s, linkage_name: param.linkage_name.to_s, line: nil) },
      locals: collect_locals(function.body)
    )
  end

  new(binary_path:, program_source_path: ir_program.source_path, functions:)
end

.load(path) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/milk_tea/tooling/debug_map.rb', line 52

def self.load(path)
  resolved_path = File.expand_path(path)
  base_dir = File.dirname(resolved_path)
  payload = JSON.parse(File.read(resolved_path))
  functions = Array(payload["functions"]).map do |function|
    Function.new(
      name: function.fetch("name"),
      linkage_name: function.fetch("cName"),
      source_path: path_from_payload(function["sourcePath"], base_dir),
      line: function["line"],
      params: load_entries(function["params"]),
      locals: load_entries(function["locals"])
    )
  end

  new(
    binary_path: path_from_payload(payload["binaryPath"], base_dir),
    program_source_path: path_from_payload(payload["programSourcePath"], base_dir),
    functions:
  )
end

.load_for_binary(binary_path) ⇒ Object



74
75
76
77
78
79
80
81
# File 'lib/milk_tea/tooling/debug_map.rb', line 74

def self.load_for_binary(binary_path)
  path = sidecar_path_for(binary_path)
  return nil unless File.file?(path)

  load(path)
rescue JSON::ParserError
  nil
end

.path_for_payload(path, base_dir) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
# File 'lib/milk_tea/tooling/debug_map.rb', line 142

def self.path_for_payload(path, base_dir)
  return nil unless path
  return path.tr("\\", "/") unless base_dir

  expanded_path = File.expand_path(path)
  begin
    Pathname.new(expanded_path).relative_path_from(Pathname.new(base_dir)).to_s.tr("\\", "/")
  rescue ArgumentError
    expanded_path.tr("\\", "/")
  end
end

.path_from_payload(path, base_dir) ⇒ Object



154
155
156
157
158
159
160
161
# File 'lib/milk_tea/tooling/debug_map.rb', line 154

def self.path_from_payload(path, base_dir)
  return nil unless path
  return path if path.empty?
  return path if absolute_path_string?(path)
  return path unless base_dir

  File.expand_path(path, base_dir)
end

.sidecar_path_for(binary_path) ⇒ Object



48
49
50
# File 'lib/milk_tea/tooling/debug_map.rb', line 48

def self.sidecar_path_for(binary_path)
  "#{File.expand_path(binary_path)}.mtdbg.json"
end

Instance Method Details

#function_for_c_name(c_name) ⇒ Object



106
107
108
# File 'lib/milk_tea/tooling/debug_map.rb', line 106

def function_for_c_name(c_name)
  @functions_by_c_name[c_name.to_s]
end

#source_variable_for(function_c_name, source_name) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
# File 'lib/milk_tea/tooling/debug_map.rb', line 118

def source_variable_for(function_c_name, source_name)
  function = function_for_c_name(function_c_name)
  return nil unless function

  matches = (function.params + function.locals).select { |entry| entry.name == source_name.to_s }
  return nil if matches.empty?

  return matches.first if matches.map(&:linkage_name).uniq.one?

  nil
end

#to_h(base_dir: nil) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
# File 'lib/milk_tea/tooling/debug_map.rb', line 130

def to_h(base_dir: nil)
  payload = {
    "version" => VERSION,
    "functions" => functions.map { |function| function.to_h(base_dir:) },
  }
  serialized_binary_path = self.class.path_for_payload(binary_path, base_dir)
  payload["binaryPath"] = serialized_binary_path if serialized_binary_path
  serialized_program_source_path = self.class.path_for_payload(program_source_path, base_dir)
  payload["programSourcePath"] = serialized_program_source_path if serialized_program_source_path
  payload
end

#variable_for(function_c_name, c_name) ⇒ Object



110
111
112
113
114
115
116
# File 'lib/milk_tea/tooling/debug_map.rb', line 110

def variable_for(function_c_name, c_name)
  function = function_for_c_name(function_c_name)
  return nil unless function

  function.params.find { |entry| entry.linkage_name == c_name.to_s } ||
    function.locals.find { |entry| entry.linkage_name == c_name.to_s }
end

#write(path) ⇒ Object



99
100
101
102
103
104
# File 'lib/milk_tea/tooling/debug_map.rb', line 99

def write(path)
  resolved_path = File.expand_path(path)
  base_dir = File.dirname(resolved_path)
  FileUtils.mkdir_p(base_dir)
  File.write(resolved_path, JSON.pretty_generate(to_h(base_dir:)) + "\n")
end