1 | var grlib = require('graphlib')
|
2 |
|
3 | import _ from 'lodash'
|
4 | import * as graphtools from '@buggyorg/graphtools'
|
5 |
|
6 | function isInPort (node) {
|
7 | return (node['nodeType'] === 'inPort' && node['isTrans'] === undefined && !node['isTrans'])
|
8 | }
|
9 |
|
10 | function isOutPort (node) {
|
11 | return (node['nodeType'] === 'outPort' && node['isTrans'] === undefined && !node['isTrans'])
|
12 | }
|
13 |
|
14 | function cloneGraph (graph) {
|
15 | return grlib.json.read(grlib.json.write(graph))
|
16 | }
|
17 |
|
18 | function processOfEdge (edgeName) {
|
19 | return edgeName.split('_').slice(0, -2).join('_')
|
20 | }
|
21 |
|
22 | function portOfEdge (graph, nodeName) {
|
23 |
|
24 | return graph.node(nodeName)['portName']
|
25 | }
|
26 |
|
27 | function getOutputs (graph, nodeId) {
|
28 | return graph.node(nodeId)['outputPorts']
|
29 | }
|
30 |
|
31 | function getInputs (graph, nodeId) {
|
32 | return graph.node(nodeId)['inputPorts']
|
33 | }
|
34 |
|
35 | var replaceAll = (str, search, replacement) => {
|
36 | var repl = replacement
|
37 | return str.split(search).join(repl)
|
38 | }
|
39 |
|
40 | function isGeneric (name) {
|
41 | return name === 'generic' || name === '[generic]'
|
42 | }
|
43 |
|
44 | export function replaceGeneric (what, replacement) {
|
45 | if (what === 'function' && typeof (replacement) === 'object') {
|
46 | return replacement
|
47 | } else if (what !== 'function' && typeof (replacement) === 'string') {
|
48 | return replaceAll(what, 'generic', replacement)
|
49 | } else {
|
50 | return what
|
51 | }
|
52 | }
|
53 |
|
54 | function entangleType (type, template) {
|
55 | if (template[0] === '[' && template[template.length - 1] === ']') {
|
56 | return type.replace(/\[/g, '').replace(/\]/g, '')
|
57 | } else {
|
58 | return type
|
59 | }
|
60 | }
|
61 |
|
62 | function tangleType (type, template) {
|
63 | if (template[0] === '[' && template[template.length - 1] === ']') {
|
64 | return '[' + type + ']'
|
65 | } else {
|
66 | return type
|
67 | }
|
68 | }
|
69 |
|
70 | function genericInputs (graph, node) {
|
71 | var genericInputs = []
|
72 | if (graph.node(node)['inputPorts'] !== undefined) {
|
73 | var inputPorts = graph.node(node)['inputPorts']
|
74 | var inputNames = Object.keys(inputPorts)
|
75 | for (var i = 0; i < inputNames.length; i++) {
|
76 | if (isGeneric(inputPorts[inputNames[i]])) {
|
77 | genericInputs = genericInputs.concat([inputNames[i]])
|
78 | }
|
79 | }
|
80 | }
|
81 | return genericInputs
|
82 | }
|
83 |
|
84 | function genericOutputs (graph, node) {
|
85 | var genericOutputs = []
|
86 | if (graph.node(node)['outputPorts'] !== undefined) {
|
87 | var outputPorts = graph.node(node)['outputPorts']
|
88 | var outputNames = Object.keys(outputPorts)
|
89 | for (var i = 0; i < outputNames.length; i++) {
|
90 | if (isGeneric(outputPorts[outputNames[i]])) {
|
91 | genericOutputs = genericOutputs.concat([outputNames[i]])
|
92 | }
|
93 | }
|
94 | }
|
95 | return genericOutputs
|
96 | }
|
97 |
|
98 | function genericInputPorts (graph, node, port) {
|
99 | var curNode = graph.node(node)
|
100 | if (curNode.inputPorts[port]) {
|
101 | return _.filter(genericInputs(graph, node), (follow) => follow === port)
|
102 | } else if (!curNode.atomic && !curNode.recursive) {
|
103 | return _.filter(genericOutputs(graph, node), (follow) => follow === port)
|
104 | } else {
|
105 | return genericInputs(graph, node)
|
106 | }
|
107 | }
|
108 |
|
109 | function genericOutputPorts (graph, node, port) {
|
110 | var curNode = graph.node(node)
|
111 | if (curNode.outputPorts[port]) {
|
112 | return _.filter(genericOutputs(graph, node), (follow) => follow === port)
|
113 | } else {
|
114 | return _.filter(genericInputs(graph, node), (follow) => follow === port)
|
115 | }
|
116 | }
|
117 |
|
118 | function followGenerics (graph, node, port) {
|
119 | var curNode = graph.node(node)
|
120 | if (port && !isGeneric(curNode.inputPorts[port]) && !isGeneric(curNode.outputPorts[port])) {
|
121 | return []
|
122 | } else {
|
123 | return genericInputPorts(graph, node, port)
|
124 | }
|
125 | }
|
126 |
|
127 | function followGenericsForward (graph, node, port) {
|
128 | var curNode = graph.node(node)
|
129 | if (port && !isGeneric(curNode.inputPorts[port]) && !isGeneric(curNode.outputPorts[port])) {
|
130 | return []
|
131 | } else {
|
132 | return genericOutputPorts(graph, node, port)
|
133 | }
|
134 | }
|
135 |
|
136 | function replaceTypeHints (graph) {
|
137 | var editGraph = graphtools.utils.edit(graph)
|
138 | _.merge(editGraph, {nodes: _.map(editGraph.nodes, (n) => {
|
139 | if (!n.value.id) { return n }
|
140 | return _.merge({}, n, { value: {
|
141 | inputPorts: _.mapValues(n.value.inputPorts, (p, k) => (n.value.typeHint && n.value.typeHint[k]) ? n.value.typeHint[k] : p),
|
142 | outputPorts: _.mapValues(n.value.outputPorts, (p, k) => (n.value.typeHint && n.value.typeHint[k]) ? n.value.typeHint[k] : p)
|
143 | }})
|
144 | })})
|
145 | return graphtools.utils.finalize(editGraph)
|
146 | }
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 |
|
153 |
|
154 |
|
155 | export function replaceGenericInput (graph, node) {
|
156 | var curNode = graph.node(node)
|
157 | var ports
|
158 | if (curNode.atomic) {
|
159 | ports = curNode.inputPorts
|
160 | } else {
|
161 | ports = _.merge({}, curNode.inputPorts, curNode.outputPorts)
|
162 | }
|
163 | return _.filter(_.flatten(_.map(ports, (type, port) =>
|
164 | _.map(graphtools.walk.walkBack(graph, {node, port}, followGenerics, {keepPorts: true}),
|
165 | (path) =>
|
166 | _.reject(path, (n) => n.port === null))))
|
167 | , (list) => list.length > 1)
|
168 | }
|
169 |
|
170 | export function replaceGenericOutput (graph, node) {
|
171 | var curNode = graph.node(node)
|
172 | var ports
|
173 | if (curNode.atomic) {
|
174 | ports = curNode.outputPorts
|
175 | } else {
|
176 | ports = _.merge({}, curNode.inputPorts, curNode.outputPorts)
|
177 | }
|
178 | return _.filter(_.flatten(_.map(ports, (type, port) =>
|
179 | graphtools.walk.walk(graph, {node, port}, followGenericsForward, {keepPorts: true})))
|
180 | , (list) => list.length > 1)
|
181 | }
|
182 |
|
183 | export function replaceGenerics (graph) {
|
184 | var processGraph = replaceTypeHints(graph)
|
185 | var nodes = processGraph.nodes()
|
186 | for (var j = 0; j < nodes.length; j++) {
|
187 | var paths = replaceGenericInput(graph, nodes[j])
|
188 |
|
189 | var pathsToReplace = []
|
190 | for (let i = 0; i < paths.length; i++) {
|
191 | var currentPath = paths[i]
|
192 | var type = processGraph.node(currentPath[0].node).outputPorts[currentPath[0].port] ||
|
193 | processGraph.node(currentPath[0].node).inputPorts[currentPath[0].port]
|
194 | |
195 |
|
196 |
|
197 |
|
198 |
|
199 |
|
200 | if (type === 'generic') {
|
201 | pathsToReplace = pathsToReplace.concat([currentPath])
|
202 | } else {
|
203 | var validType = type
|
204 | replacePathGenerics(processGraph, currentPath, type)
|
205 | }
|
206 | }
|
207 | if (validType === undefined && pathsToReplace.length !== 0) {
|
208 | var secondInputs = processGraph.node(nodes[j]).inputPorts
|
209 | var keys = Object.keys(secondInputs)
|
210 | for (let i = 0; i < keys.length; i++) {
|
211 | if (secondInputs[keys[i]] !== 'generic') {
|
212 | validType = secondInputs[keys[i]]
|
213 | }
|
214 | }
|
215 | }
|
216 | if (validType === undefined && pathsToReplace.length !== 0) {
|
217 | paths = replaceGenericOutput(graph, nodes[j])
|
218 | for (let i = 0; i < paths.length; i++) {
|
219 | currentPath = _.filter(paths[i], (node) => node.port !== null)
|
220 | type = processGraph.node(currentPath[currentPath.length - 1].node).inputPorts[currentPath[currentPath.length - 1].port] ||
|
221 | processGraph.node(currentPath[currentPath.length - 1].node).outputPorts[currentPath[currentPath.length - 1].port]
|
222 | if (type === 'generic') {
|
223 | pathsToReplace = pathsToReplace.concat([currentPath])
|
224 | } else {
|
225 | validType = type
|
226 | replacePathGenerics(processGraph, currentPath, type)
|
227 | }
|
228 | }
|
229 | }
|
230 | if (validType === undefined && pathsToReplace.length !== 0) {
|
231 | throw new Error('Generics could not be replaced: No type found.')
|
232 | }
|
233 | for (let p = 0; p < pathsToReplace.length; p++) {
|
234 | replacePathGenerics(processGraph, pathsToReplace[p], validType, pathsToReplace[p][0].port)
|
235 | }
|
236 | }
|
237 | return processGraph
|
238 | }
|
239 |
|
240 | function replacePathGenerics (graph, path, type, firstPort) {
|
241 | for (var r = 0; r < path.length - 1; r++) {
|
242 | var fromNode = graph.node(path[r].edge.from)
|
243 | var fromType = (graph.parent(path[r].edge.to) === path[r].edge.from) ? fromNode.inputPorts[path[r].edge.outPort] : fromNode.outputPorts[path[r].edge.outPort]
|
244 |
|
245 | if (isGeneric(fromType)) {
|
246 |
|
247 | if (!fromNode.generic || !fromNode.genericType) {
|
248 | throw new Error('Cannot resolve generic type for ' + path[r].edge.from + ' on path ' + JSON.stringify(path))
|
249 | }
|
250 | fromType = tangleType(fromNode.genericType, fromType)
|
251 | fromNode.outputPorts[path[r].edge.outPort] = fromType
|
252 | }
|
253 | var toNode = graph.node(path[r].edge.to)
|
254 | var toType = (graph.parent(path[r].edge.from) === path[r].edge.to) ? toNode.outputPorts[path[r].edge.inPort] : toNode.inputPorts[path[r].edge.inPort]
|
255 | var entangled = toNode.genericType || entangleType(fromType, toType)
|
256 | toNode.generic = true
|
257 | toNode.genericType = entangled
|
258 | if (isGeneric(toType)) {
|
259 | toNode.inputPorts[path[r].edge.inPort] = tangleType(entangled, toType)
|
260 | }
|
261 | }
|
262 | var lastNode = path[path.length - 1].node
|
263 | var genOutput = genericOutputs(graph, lastNode)
|
264 | for (var l = 0; l < genOutput.length; l++) {
|
265 | graph.node(lastNode).outputPorts[genOutput[l]] =
|
266 | tangleType(graph.node(lastNode).genericType, graph.node(lastNode).outputPorts[genOutput[l]])
|
267 | }
|
268 | |
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 |
|
296 | }
|
297 |
|
298 | export function addTypeConversion (processGraph, convertGraph) {
|
299 | var newProcessGraph = cloneGraph(processGraph)
|
300 |
|
301 | for (let edge of newProcessGraph.edges()) {
|
302 |
|
303 | var v = newProcessGraph.node(edge.v)
|
304 | var w = newProcessGraph.node(edge.w)
|
305 | if (isOutPort(v) && isInPort(w)) {
|
306 | var labelIn = edge.v
|
307 | var labelOut = edge.w
|
308 |
|
309 | var processV = processOfEdge(labelIn)
|
310 | var processW = processOfEdge(labelOut)
|
311 | var portNameV = portOfEdge(newProcessGraph, labelIn)
|
312 | var portNameW = portOfEdge(newProcessGraph, labelOut)
|
313 | var typeV = getOutputs(newProcessGraph, processV)[portNameV]
|
314 | var typeW = getInputs(newProcessGraph, processW)[portNameW]
|
315 |
|
316 |
|
317 | if (typeV !== typeW) {
|
318 | newProcessGraph.removeEdge(labelIn, labelOut)
|
319 | var parentV = processGraph.parent(labelIn)
|
320 |
|
321 | var dijkstra = grlib.alg.dijkstra(convertGraph, typeV)
|
322 | var way = [typeW]
|
323 | while (way[way.length - 1] !== typeV) {
|
324 | way.push(dijkstra[way[way.length - 1]].predecessor)
|
325 | }
|
326 | for (var k = way.length - 1; k >= 1; k--) {
|
327 | var number = way.length - k
|
328 | var id = labelIn + ':' + labelOut + '_' + number
|
329 |
|
330 |
|
331 | var meta = 'translator/' + way[k] + '_to_' + way[k - 1]
|
332 | newProcessGraph.setNode(id, {'nodeType': 'process', 'id': meta, 'atomic': 'true'})
|
333 | newProcessGraph.setNode(id + '_PORT_in', {'nodeType': 'inPort', 'portName': 'input', 'isTrans': true})
|
334 | newProcessGraph.setNode(id + '_PORT_out', {'nodeType': 'outPort', 'portName': 'output', 'isTrans': true})
|
335 |
|
336 | newProcessGraph.setEdge(id + '_PORT_in', id)
|
337 | newProcessGraph.setEdge(id, id + '_PORT_out')
|
338 |
|
339 | newProcessGraph.setParent(id, parentV)
|
340 | newProcessGraph.setParent(id + '_PORT_in', parentV)
|
341 | newProcessGraph.setParent(id + '_PORT_out', parentV)
|
342 |
|
343 |
|
344 | if (k === way.length - 1) {
|
345 | newProcessGraph.setEdge(labelIn, id + '_PORT_in')
|
346 | } else {
|
347 | newProcessGraph.setEdge(labelIn + ':' + labelOut + '_' + (number - 1) + '_PORT_out', id + '_PORT_in')
|
348 | }
|
349 |
|
350 | if (k === 1) {
|
351 | newProcessGraph.setEdge(id + '_PORT_out', labelOut)
|
352 | }
|
353 | }
|
354 | }
|
355 | }
|
356 | }
|
357 | return newProcessGraph
|
358 | }
|
359 |
|
360 | export function genericNodes (graph) {
|
361 | var nodes = graph.nodes()
|
362 | var genNodes = []
|
363 | for (let i = 0; i < nodes.length; i++) {
|
364 | var inp = graph.node(nodes[i]).inputPorts
|
365 | if (inp !== undefined) {
|
366 | var inpKeys = Object.keys(inp)
|
367 | for (let j = 0; j < inpKeys.length; j++) {
|
368 | if (isGeneric(inp[inpKeys[j]])) {
|
369 | genNodes.push(nodes[i])
|
370 | }
|
371 | }
|
372 | }
|
373 | var outp = graph.node(nodes[i]).outputPorts
|
374 | if (outp !== undefined) {
|
375 | var outpKeys = Object.keys(outp)
|
376 | for (let j = 0; j < outpKeys.length; j++) {
|
377 | if (isGeneric(outp[outpKeys[j]])) {
|
378 | genNodes.push(nodes[i])
|
379 | }
|
380 | }
|
381 | }
|
382 | }
|
383 | return genNodes
|
384 | }
|
385 |
|
386 | export function isGenericFree (graph) {
|
387 | var nodes = graph.nodes()
|
388 | for (let i = 0; i < nodes.length; i++) {
|
389 | var inp = graph.node(nodes[i]).inputPorts
|
390 | if (inp !== undefined) {
|
391 | var inpKeys = Object.keys(inp)
|
392 | for (let j = 0; j < inpKeys.length; j++) {
|
393 | if (isGeneric(inp[inpKeys[j]])) {
|
394 | return false
|
395 | }
|
396 | }
|
397 | }
|
398 | var outp = graph.node(nodes[i]).outputPorts
|
399 | if (outp !== undefined) {
|
400 | var outpKeys = Object.keys(outp)
|
401 | for (let j = 0; j < outpKeys.length; j++) {
|
402 | if (isGeneric(outp[outpKeys[j]])) {
|
403 | return false
|
404 | }
|
405 | }
|
406 | }
|
407 | }
|
408 | return true
|
409 | }
|