Class: Mxrb::Semantic::Inliner

Inherits:
Object
  • Object
show all
Defined in:
lib/mxrb/semantic/inliner.rb

Overview

Inlines a called microflow back into its caller, replacing the call_microflow activity with the callee's activities.

Constant Summary collapse

FLOW_KINDS =
%i[microflow nanoflow].freeze

Instance Method Summary collapse

Constructor Details

#initialize(project) ⇒ Inliner

Returns a new instance of Inliner.



117
118
119
# File 'lib/mxrb/semantic/inliner.rb', line 117

def initialize(project)
  @project = project
end

Instance Method Details

#plan(source_name, calling:) ⇒ Object

Parameters:

  • source_name (String)

    qualified name of the caller microflow

  • calling (String)

    qualified name of the microflow to inline

Raises:

  • (ArgumentError)


123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/mxrb/semantic/inliner.rb', line 123

def plan(source_name, calling:)
  source = resolve(source_name)
  raise ArgumentError, "#{source.qualified_name} is not a microflow or nanoflow" \
    unless FLOW_KINDS.include?(source.kind)

  called = resolve(calling)
  raise ArgumentError, "#{called.qualified_name} is not a microflow or nanoflow" \
    unless FLOW_KINDS.include?(called.kind)

  source_raw = @project.raw_unit(source.unit_id)
  source_doc = @project.parse_bson(source_raw)

  all_objects = normalize_all(parse_array(source_doc["ObjectCollection"]["Objects"]))
  all_flows   = normalize_all(parse_array(source_doc["Flows"]))

  call_activity = all_objects.find do |obj|
    obj["$Type"] == "Microflows$ActionActivity" &&
      obj.dig("Action", "MicroflowCall", "Microflow") == calling
  end
  raise ArgumentError, "no call to #{calling.inspect} found in #{source_name.inspect}" \
    unless call_activity

  call_id    = call_activity["$ID"]
  entry_flow = all_flows.find { _1["DestinationPointer"] == call_id }
  exit_flow  = all_flows.find { _1["OriginPointer"]      == call_id }
  raise ArgumentError, "cannot find entry flow for call activity" unless entry_flow
  raise ArgumentError, "cannot find exit flow for call activity"  unless exit_flow

  called_raw = @project.raw_unit(called.unit_id)
  called_doc = @project.parse_bson(called_raw)

  called_objects = normalize_all(parse_array(called_doc["ObjectCollection"]["Objects"]))
  called_flows   = normalize_all(parse_array(called_doc["Flows"]))

  start_event = called_objects.find { _1["$Type"] == "Microflows$StartEvent" }
  end_event   = called_objects.find { _1["$Type"] == "Microflows$EndEvent"   }
  raise ArgumentError, "#{calling.inspect} has no start event" unless start_event
  raise ArgumentError, "#{calling.inspect} has no end event" unless end_event

  start_id = start_event["$ID"]
  end_id   = end_event["$ID"]

  inner_objects = called_objects.reject { [start_id, end_id].include?(_1["$ID"]) }
  raise ArgumentError, "#{calling.inspect} has no activities to inline" if inner_objects.empty?

  inner_flows = called_flows.reject do |f|
    [start_id, end_id].include?(f["OriginPointer"]) ||
      [start_id, end_id].include?(f["DestinationPointer"])
  end

  start_to_first = called_flows.find { _1["OriginPointer"]      == start_id }
  last_to_end    = called_flows.find { _1["DestinationPointer"] == end_id   }
  raise ArgumentError, "cannot resolve first activity in #{calling.inspect}" unless start_to_first
  raise ArgumentError, "cannot resolve last activity in #{calling.inspect}" unless last_to_end

  # Synthesise entry/exit flows that point at the actual first/last inlined objects
  synthetic_entry = entry_flow.merge("DestinationPointer" => start_to_first["DestinationPointer"])
  synthetic_exit  = exit_flow.merge("OriginPointer"       => last_to_end["OriginPointer"])

  InlinePlan.new(
    project:       @project,
    source:        source,
    called_name:   calling,
    call_id:       call_id,
    inner_objects: inner_objects,
    inner_flows:   inner_flows,
    entry_flow:    synthetic_entry,
    exit_flow:     synthetic_exit,
    source_doc:    source_doc,
    source_raw:    source_raw
  )
end