Skip to content

Commit

Permalink
Merge pull request #2107 from xodio/feat-1144-variadic-pass
Browse files Browse the repository at this point in the history
Introduce variadic-pass nodes
  • Loading branch information
evgenykochetkov committed Mar 10, 2021
2 parents 16dd97c + b17f125 commit 19203cd
Show file tree
Hide file tree
Showing 23 changed files with 2,034 additions and 135 deletions.
1 change: 1 addition & 0 deletions packages/xod-arduino/src/transpiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,7 @@ const transformProjectWithImpls = def(
R.map(XP.extractBoundInputsToConstNodes(path)),
R.chain(XP.flatten(R.__, path)),
R.map(XP.expandVariadicNodes(path)),
R.map(XP.expandVariadicPassNodes(path)),
R.map(XP.linkifyPatchRecursively(path)),
R.chain(XP.autoresolveTypes(path)),
R.unless(
Expand Down
32 changes: 25 additions & 7 deletions packages/xod-client/src/hinting/validation.funcs.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const getMarkerNodesErrorMap = (predicate, validator, errorType) => patch => {
mergeAllWithConcat,
R.map(R.objOf(R.__, { [errorType]: [err] }))
)(markerNodeIds),
R.always({ [errorType]: [] }),
R.always({}),
validator(patch)
);
};
Expand All @@ -35,12 +35,30 @@ const getMarkerNodesErrorMap = (predicate, validator, errorType) => patch => {
//
// =============================================================================

// :: Patch -> Map NodeId (Map ErrorType [Error])
export const getVariadicMarkersErrorMap = getMarkerNodesErrorMap(
R.pipe(XP.getNodeType, XP.isVariadicPath),
XP.validatePatchForVariadics,
'validatePatchForVariadics'
);
// :: Patch -> Project -> Map NodeId (Map ErrorType [Error])
export const getVariadicMarkersErrorMap = (patch, project) =>
R.compose(
foldEither(
err =>
R.compose(
R.fromPairs,
R.map(markerNodeId => [
markerNodeId,
{ validatePatchForVariadics: [err] },
]),
R.map(XP.getNodeId),
R.filter(
R.pipe(
XP.getNodeType,
R.either(XP.isVariadicPath, XP.isVariadicPassPath)
)
),
XP.listNodes
)(patch),
R.always({})
),
XP.validatePatchForVariadics
)(project, patch);

// :: Patch -> Map NodeId (Map ErrorType [Error])
export const getAbstractMarkersErrorMap = getMarkerNodesErrorMap(
Expand Down
12 changes: 12 additions & 0 deletions packages/xod-project/built-in-patches.xodball
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,18 @@
"@/variadic-3": {
"description": "Makes three rightmost inputs of the patch node containing this node variadic",
"path": "@/variadic-3"
},
"@/variadic-pass-1": {
"description": "Makes the rightmost input of the patch node containing this node variadic, passing the arity level down to the variadic nodes to which they link",
"path": "@/variadic-pass-1"
},
"@/variadic-pass-2": {
"description": "Makes two rightmost inputs of the patch node containing this node variadic, passing the arity level down to the variadic nodes to which they link",
"path": "@/variadic-pass-2"
},
"@/variadic-pass-3": {
"description": "Makes three rightmost inputs of the patch node containing this node variadic, passing the arity level down to the variadic nodes to which they link",
"path": "@/variadic-pass-3"
}
}
}
2 changes: 1 addition & 1 deletion packages/xod-project/src/expandVariadicNodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const nodeIdLens = R.lens(Node.getNodeId, R.assoc('id'));

// helpers for creating nodes inside expanded patch

const createAdditionalValueTerminalGroups = (
export const createAdditionalValueTerminalGroups = (
patch,
desiredArityLevel,
originalTerminalNodes,
Expand Down
231 changes: 231 additions & 0 deletions packages/xod-project/src/expandVariadicPassNodes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import * as R from 'ramda';
import { Maybe } from 'ramda-fantasy';
import { explodeEither, isAmong } from 'xod-func-tools';

import { def } from './types';

import * as Pin from './pin';
import * as Node from './node';
import * as Link from './link';
import * as Patch from './patch';
import * as Project from './project';
import {
getExpandedVariadicPatchPath,
isVariadicPassPath,
} from './patchPathUtils';
import { createAdditionalValueTerminalGroups } from './expandVariadicNodes';

const expandPassPatch = R.curry((desiredArityLevel, patch) => {
const expandedPatchPath = R.compose(
getExpandedVariadicPatchPath(desiredArityLevel),
Patch.getPatchPath
)(patch);

// :: {
// acc :: [Pin],
// value :: [Pin],
// shared :: [Pin],
// outputs :: [Pin],
// }
const variadicPins = R.compose(
R.map(R.sortBy(Pin.getPinOrder)),
explodeEither,
Patch.computeVariadicPins
)(patch);

// :: [Node]
const originalTerminalNodes = R.compose(
R.filter(Node.isPinNode),
Patch.listNodes
)(patch);

// :: [ [Node] ]
const additionalValueTerminalGroups = createAdditionalValueTerminalGroups(
patch,
desiredArityLevel,
originalTerminalNodes,
variadicPins
);

const variadicPinKeys = R.compose(R.map(Pin.getPinKey), R.prop('value'))(
variadicPins
);

const linksFromVariadicOutputs = R.compose(
R.filter(R.pipe(Link.getLinkOutputNodeId, isAmong(variadicPinKeys))),
Patch.listLinks
)(patch);
const nodesConnectedToVariadicInputs = R.compose(
R.map(nodeId => Patch.getNodeByIdUnsafe(nodeId, patch)),
R.uniq,
R.map(Link.getLinkInputNodeId)
)(linksFromVariadicOutputs);

const arityLens = R.lens(Node.getNodeArityLevel, Node.setNodeArityLevel);
const variadicNodesWithAddedArity = R.map(
R.over(arityLens, R.add(desiredArityLevel - 1)),
nodesConnectedToVariadicInputs
);

const linksFromAdditionalTerminalsToNodesWithAddedArity = R.compose(
R.chain(terminalGroupIndex =>
R.map(link => {
const inputNodeId = Link.getLinkInputNodeId(link);
const inputPinKey = R.compose(
R.over(Pin.variadicPinKeySuffixLens, R.add(terminalGroupIndex)),
Link.getLinkInputPinKey
)(link);
const outputNodeId = R.compose(
Pin.addVariadicPinKeySuffix(terminalGroupIndex),
Link.getLinkOutputNodeId
)(link);
const outputPinKey = Link.getLinkOutputPinKey(link);

return Link.createLink(
inputPinKey,
inputNodeId,
outputPinKey,
outputNodeId
);
})(linksFromVariadicOutputs)
),
R.range(1) // [1; desiredArityLevel)
)(desiredArityLevel);

const markerNode = R.compose(
R.find(R.pipe(Node.getNodeType, isVariadicPassPath)),
Patch.listNodes
)(patch);

return R.compose(
Patch.dissocNode(markerNode),
Patch.upsertLinks(linksFromAdditionalTerminalsToNodesWithAddedArity),
Patch.upsertNodes([
...R.unnest(additionalValueTerminalGroups),
...variadicNodesWithAddedArity,
]),
Patch.setPatchPath(expandedPatchPath)
)(patch);
});

//
// expand all patches(starting from a specified entry patch)
//

const traverseExpandableQueue = (
queue,
processed,
acc,
processQueueElement
) => {
if (R.isEmpty(queue)) return acc;

const [currentQueueElement, ...restQueueElements] = queue;

if (R.contains(currentQueueElement, processed))
return traverseExpandableQueue(
restQueueElements,
processed,
acc,
processQueueElement
);

const { result, additionalQueueElements } = processQueueElement(
currentQueueElement,
acc
);

const updatedQueue = R.compose(
R.uniq,
R.concat(restQueueElements),
R.difference(R.__, processed)
)(additionalQueueElements);

return traverseExpandableQueue(
updatedQueue,
R.append(currentQueueElement, processed),
result,
processQueueElement
);
};

export default def(
'expandVariadicPassNodes :: PatchPath -> Project -> Project',
(entryPatchPath, initialProject) =>
traverseExpandableQueue(
[entryPatchPath],
[],
initialProject,
(currentPatchPath, project) => {
const initialPatch = Project.getPatchByPathUnsafe(
currentPatchPath,
project
);

const nodesToExpand = R.compose(
R.filter(
R.compose(
Patch.isVariadicPassPatch,
Project.getPatchByPathUnsafe(R.__, project),
Node.getNodeType
)
),
R.filter(R.pipe(Node.getNodeArityLevel, al => al > 1)),
Patch.listNodes
)(initialPatch);
// TODO: short-cirquit if nodesToExpand is empty?

const expandedPatches = R.compose(
R.map(({ patchPath, desiredArityLevel }) =>
R.compose(
expandPassPatch(desiredArityLevel),
Project.getPatchByPathUnsafe(patchPath)
)(project)
),
R.reject(({ patchPath, desiredArityLevel }) =>
R.compose(
Maybe.isJust,
Project.getPatchByPath(
getExpandedVariadicPatchPath(desiredArityLevel, patchPath)
)
)(project)
),
R.uniq,
R.map(
R.applySpec({
patchPath: Node.getNodeType,
desiredArityLevel: Node.getNodeArityLevel,
})
)
)(nodesToExpand);

const updatedPatch = R.compose(
Patch.upsertNodes(R.__, initialPatch),
R.map(node =>
R.compose(
Node.setNodeArityLevel(1),
Node.setNodeType(
getExpandedVariadicPatchPath(
Node.getNodeArityLevel(node),
Node.getNodeType(node)
)
)
)(node)
)
)(nodesToExpand);

const additionalQueueElements = R.compose(
R.map(Node.getNodeType),
Patch.listNodes
)(updatedPatch);

return {
result: Project.upsertPatches(
[updatedPatch, ...expandedPatches],
project
),
additionalQueueElements,
};
}
)
);
2 changes: 1 addition & 1 deletion packages/xod-project/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ export {
listVariadicValuePins,
listVariadicAccPins,
listVariadicSharedPins,
validatePatchForVariadics,
getArityStepFromPatch,
isVariadicPatch,
isAbstractPatch,
Expand Down Expand Up @@ -127,6 +126,7 @@ export {
default as extractBoundInputsToConstNodes,
} from './extractBoundInputsToConstNodes';
export { default as expandVariadicNodes } from './expandVariadicNodes';
export { default as expandVariadicPassNodes } from './expandVariadicPassNodes';
export * from './patchPathUtils';
export * from './versionUtils';
export * from './xodball';
Expand Down
24 changes: 24 additions & 0 deletions packages/xod-project/src/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,12 @@ export default {
note: `A variadic-${arityStep} patch with ${outputCount} outputs should have at least ${minInputs} inputs`,
trace,
}),
NOT_ENOUGH_VARIADIC_PASS_INPUTS: ({ trace, arityStep }) => ({
title: 'Too few variadic inputs',
note: `A variadic-pass-${arityStep} patch should have at least ${arityStep} inputs`,
solution: 'Add inputs or delete the marker to continue.',
trace,
}),
WRONG_VARIADIC_PIN_TYPES: ({ accPinLabels, outPinLabels, trace }) => ({
title: 'Invalid variadic patch',
note: `Types of inputs ${accPinLabels.join(
Expand All @@ -214,6 +220,24 @@ export default {
note: `A variadic patch should have at least one output`,
trace,
}),
VARIADIC_PASS_CONNECTED_TO_NON_VARIADIC_NODE: ({
trace,
variadicPassPinLabel,
connectedNodeType,
}) => ({
title: 'Variadic input links to scalar node',
note: `A variadic-pass input ${variadicPassPinLabel} is linked to a non-variadic node ${connectedNodeType}`,
trace,
}),
VARIADIC_PASS_CONNECTED_TO_NON_VARIADIC_PIN: ({
trace,
variadicPassPinLabel,
connectedNodeType,
}) => ({
title: 'Variadic input links to scalar pin of a variadic node',
note: `A variadic-pass input ${variadicPassPinLabel} is linked to a non-variadic pin of node ${connectedNodeType}`,
trace,
}),

// Transpile
IMPLEMENTATION_NOT_FOUND: ({ patchPath, trace }) => ({
Expand Down

0 comments on commit 19203cd

Please sign in to comment.