From f8e0c0b637f874992ebd2884d9832540056cc45b Mon Sep 17 00:00:00 2001 From: Evgeny Kochetkov Date: Thu, 4 Mar 2021 16:40:40 +0300 Subject: [PATCH 1/2] feat(xod-project, xod-arduino): introduce variadic-pass nodes --- packages/xod-arduino/src/transpiler.js | 1 + .../src/hinting/validation.funcs.js | 32 +- packages/xod-project/built-in-patches.xodball | 12 + .../xod-project/src/expandVariadicNodes.js | 2 +- .../src/expandVariadicPassNodes.js | 231 ++++++ packages/xod-project/src/index.js | 2 +- packages/xod-project/src/messages.js | 24 + packages/xod-project/src/patch.js | 90 +-- packages/xod-project/src/patchPathUtils.js | 13 +- packages/xod-project/src/pin.js | 22 + packages/xod-project/src/project.js | 114 ++- .../test/expandVariadicNodes.spec.js | 104 +-- .../test/expandVariadicPassNodes.spec.js | 52 ++ .../expanding-variadic-pass.expected.xodball | 679 ++++++++++++++++++ .../fixtures/expanding-variadic-pass.xodball | 283 ++++++++ 15 files changed, 1526 insertions(+), 135 deletions(-) create mode 100644 packages/xod-project/src/expandVariadicPassNodes.js create mode 100644 packages/xod-project/test/expandVariadicPassNodes.spec.js create mode 100644 packages/xod-project/test/fixtures/expanding-variadic-pass.expected.xodball create mode 100644 packages/xod-project/test/fixtures/expanding-variadic-pass.xodball diff --git a/packages/xod-arduino/src/transpiler.js b/packages/xod-arduino/src/transpiler.js index 9e776b265..2743dc702 100644 --- a/packages/xod-arduino/src/transpiler.js +++ b/packages/xod-arduino/src/transpiler.js @@ -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( diff --git a/packages/xod-client/src/hinting/validation.funcs.js b/packages/xod-client/src/hinting/validation.funcs.js index b144bea61..0fec02192 100644 --- a/packages/xod-client/src/hinting/validation.funcs.js +++ b/packages/xod-client/src/hinting/validation.funcs.js @@ -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) ); }; @@ -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( diff --git a/packages/xod-project/built-in-patches.xodball b/packages/xod-project/built-in-patches.xodball index ad2a2a85c..a97fff834 100644 --- a/packages/xod-project/built-in-patches.xodball +++ b/packages/xod-project/built-in-patches.xodball @@ -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" } } } diff --git a/packages/xod-project/src/expandVariadicNodes.js b/packages/xod-project/src/expandVariadicNodes.js index baa60a923..70b2744c2 100644 --- a/packages/xod-project/src/expandVariadicNodes.js +++ b/packages/xod-project/src/expandVariadicNodes.js @@ -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, diff --git a/packages/xod-project/src/expandVariadicPassNodes.js b/packages/xod-project/src/expandVariadicPassNodes.js new file mode 100644 index 000000000..05a29066c --- /dev/null +++ b/packages/xod-project/src/expandVariadicPassNodes.js @@ -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, + }; + } + ) +); diff --git a/packages/xod-project/src/index.js b/packages/xod-project/src/index.js index c9985c179..21de5320d 100644 --- a/packages/xod-project/src/index.js +++ b/packages/xod-project/src/index.js @@ -63,7 +63,6 @@ export { listVariadicValuePins, listVariadicAccPins, listVariadicSharedPins, - validatePatchForVariadics, getArityStepFromPatch, isVariadicPatch, isAbstractPatch, @@ -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'; diff --git a/packages/xod-project/src/messages.js b/packages/xod-project/src/messages.js index 09e66919d..cf0a12b15 100644 --- a/packages/xod-project/src/messages.js +++ b/packages/xod-project/src/messages.js @@ -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( @@ -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 }) => ({ diff --git a/packages/xod-project/src/patch.js b/packages/xod-project/src/patch.js index e6798d3be..3323fc59a 100644 --- a/packages/xod-project/src/patch.js +++ b/packages/xod-project/src/patch.js @@ -44,6 +44,7 @@ import { isBuiltInLibName, isLocalMarker, isVariadicPath, + isVariadicPassPath, isExpandedVariadicPatchBasename, getArityStepFromPatchPath, getSpecializationPatchPath, @@ -1226,7 +1227,12 @@ export const patchListEqualsBy = def( */ const findVariadicPatchPath = def( 'findVariadicPatchPath :: Patch -> Maybe PatchPath', - R.compose(Maybe, R.find(isVariadicPath), R.map(Node.getNodeType), listNodes) + R.compose( + Maybe, + R.find(R.either(isVariadicPath, isVariadicPassPath)), + R.map(Node.getNodeType), + listNodes + ) ); /** @@ -1237,6 +1243,11 @@ export const isVariadicPatch = def( R.compose(Maybe.isJust, findVariadicPatchPath) ); +export const isVariadicPassPatch = def( + 'isVariadicPassPatch :: Patch -> Boolean', + R.compose(foldMaybe(false, isVariadicPassPath), findVariadicPatchPath) +); + /** * Get arity step (1/2/3) from Patch by checking for * existing of variadic node and extract its arity step from @@ -1256,7 +1267,7 @@ const checkArityMarkersAmount = def( R.compose( R.equals(1), R.length, - R.filter(isVariadicPath), + R.filter(R.either(isVariadicPath, isVariadicPassPath)), R.map(Node.getNodeType), listNodes ) @@ -1277,10 +1288,12 @@ export const computeVariadicPins = def( return fail('TOO_MANY_VARIADIC_MARKERS', { trace: [patchPath] }); } + const isVariadicPass = isVariadicPassPatch(patch); + const outputs = listOutputPins(patch); const outputCount = outputs.length; - if (outputCount === 0) { + if (!isVariadicPass && outputCount === 0) { return fail('VARIADIC_HAS_NO_OUTPUTS', { trace: [patchPath] }); } @@ -1293,13 +1306,18 @@ export const computeVariadicPins = def( getArityStepFromPatch )(patch); - if (inputCount - arityStep < outputCount) { - const minInputs = R.add(outputCount, arityStep); + const notEnoughVariadicInputs = inputCount - arityStep < outputCount; + if (!isVariadicPass && notEnoughVariadicInputs) { return fail('NOT_ENOUGH_VARIADIC_INPUTS', { trace: [patchPath], arityStep, outputCount, - minInputs, + minInputs: outputCount + arityStep, + }); + } else if (isVariadicPass && inputCount < arityStep) { + return fail('NOT_ENOUGH_VARIADIC_PASS_INPUTS', { + trace: [patchPath], + arityStep, }); } @@ -1309,30 +1327,30 @@ export const computeVariadicPins = def( R.slice(0, R.negate(arityStep)) )(inputs); - const pinLabelsOfNonEqualPinTypes = R.compose( - R.reject(R.isNil), - mapIndexed((accPin, idx) => { - const curPinType = Pin.getPinType(accPin); - const outPinType = Pin.getPinType(outputs[idx]); - return curPinType === outPinType - ? null - : [Pin.getPinLabel(accPin), Pin.getPinLabel(outputs[idx])]; - }) - )(accPins); - - if (notEmpty(pinLabelsOfNonEqualPinTypes)) { - const accPinLabels = R.pluck(0, pinLabelsOfNonEqualPinTypes); - const outPinLabels = R.pluck(1, pinLabelsOfNonEqualPinTypes); - return fail('WRONG_VARIADIC_PIN_TYPES', { - trace: [patchPath], - accPinLabels, - outPinLabels, - }); + if (!isVariadicPass) { + const pinLabelsOfNonEqualPinTypes = R.compose( + R.reject(R.isNil), + mapIndexed((accPin, idx) => { + const curPinType = Pin.getPinType(accPin); + const outPinType = Pin.getPinType(outputs[idx]); + return curPinType === outPinType + ? null + : [Pin.getPinLabel(accPin), Pin.getPinLabel(outputs[idx])]; + }) + )(accPins); + + if (notEmpty(pinLabelsOfNonEqualPinTypes)) { + const accPinLabels = R.pluck(0, pinLabelsOfNonEqualPinTypes); + const outPinLabels = R.pluck(1, pinLabelsOfNonEqualPinTypes); + return fail('WRONG_VARIADIC_PIN_TYPES', { + trace: [patchPath], + accPinLabels, + outPinLabels, + }); + } } - const sharedPins = R.slice(0, R.negate(R.add(accPins.length, arityStep)))( - inputs - ); + const sharedPins = R.slice(0, -(accPins.length + arityStep), inputs); return Either.of({ acc: accPins, @@ -1372,22 +1390,6 @@ export const listVariadicSharedPins = def( R.compose(R.pluck('shared'), computeVariadicPins) ); -/** - * Checks a patch for variadic marker existence. - * If it has variadic marker — compute and validate variadic Pins, - * and then return Either Error Patch. - * If not - just return Either.Right Patch. - */ -export const validatePatchForVariadics = def( - 'validatePatchForVariadics :: Patch -> Either Error Patch', - patch => - R.ifElse( - isVariadicPatch, - R.compose(R.map(R.always(patch)), computeVariadicPins), - Either.of - )(patch) -); - /** * Computes and returns map of pins for Node with additional Pins, * if Node has `arityLevel > 1` and Patch has a variadic markers. diff --git a/packages/xod-project/src/patchPathUtils.js b/packages/xod-project/src/patchPathUtils.js index 6c80adf76..5a59aa756 100644 --- a/packages/xod-project/src/patchPathUtils.js +++ b/packages/xod-project/src/patchPathUtils.js @@ -208,11 +208,22 @@ const variadicRegExp = new RegExp(`^${PATCH_NODES_LIB_NAME}/variadic-([1-3])`); // :: PatchPath -> Boolean export const isVariadicPath = R.test(variadicRegExp); +const variadicPassRegExp = new RegExp( + `^${PATCH_NODES_LIB_NAME}/variadic-pass-([1-3])` +); + +// :: PatchPath -> Boolean +export const isVariadicPassPath = R.test(variadicPassRegExp); + +const variadicOrPassRegExp = new RegExp( + `^${PATCH_NODES_LIB_NAME}/variadic-(?:pass-)?([1-3])` +); + // :: PatchPath -> ArityStep export const getArityStepFromPatchPath = R.compose( x => parseInt(x, 10), R.nth(1), - R.match(variadicRegExp) + R.match(variadicOrPassRegExp) ); // :: NonZeroNaturalNumber -> PatchPath diff --git a/packages/xod-project/src/pin.js b/packages/xod-project/src/pin.js index eeed33767..c089b841c 100644 --- a/packages/xod-project/src/pin.js +++ b/packages/xod-project/src/pin.js @@ -324,6 +324,28 @@ export const addVariadicPinKeySuffix = def( (index, key) => `${key}-$${index}` ); +const parseVariadicPinKey = pinKey => { + const [, base, , indexStr = '0'] = R.match( + /([A-Za-z0-9_-]*)(-\$(\d+))?$/, + pinKey + ); + return { + base, + index: parseInt(indexStr, 10), + }; +}; + +export const variadicPinKeySuffixLens = R.lens( + R.pipe(parseVariadicPinKey, R.prop('index')), + (newIndex, pinKey) => { + const { base } = parseVariadicPinKey(pinKey); + + if (newIndex === 0) return base; + + return addVariadicPinKeySuffix(newIndex, base); + } +); + /** * (!) This function should be called only for variadic pins, * that should have an updated label. diff --git a/packages/xod-project/src/project.js b/packages/xod-project/src/project.js index ccea5dc13..543065a45 100644 --- a/packages/xod-project/src/project.js +++ b/packages/xod-project/src/project.js @@ -736,6 +736,118 @@ const checkPatchForDeadLinksAndPins = def( } ); +export const validatePatchForVariadics = def( + 'validatePatchForVariadics :: Project -> Patch -> Either Error Patch', + (project, validatedPatch) => { + if (!Patch.isVariadicPatch(validatedPatch)) { + return Either.of(validatedPatch); + } + + return Patch.computeVariadicPins(validatedPatch).chain( + ({ value: variadicPins }) => { + // `computeVariadicPins` checks for all possible errors in "regular" variadic patches. + // We only need to do additional checks for variadic-pass patches. + if (!Patch.isVariadicPassPatch(validatedPatch)) { + return Either.of(validatedPatch); + } + + const normalizedPinLabelsByPinKey = R.compose( + R.map(Pin.getPinLabel), + R.indexBy(Pin.getPinKey), + Pin.normalizeEmptyPinLabels, + Patch.listPins + )(validatedPatch); + + const variadicPassPinsByKey = R.indexBy(Pin.getPinKey, variadicPins); + + return R.compose( + R.map(R.head), + R.sequence(Either.of), + R.map(link => { + const variadicPassPinLabel = R.compose( + R.prop(R.__, normalizedPinLabelsByPinKey), + Link.getLinkOutputNodeId + )(link); + + return R.compose( + foldMaybe( + // If we got here, some variadic pins + // are connected to a dead node. + // That's a job for other validators. + Either.of(validatedPatch), + ([connectedNodeType, connectedPatch]) => { + if (!Patch.isVariadicPatch(connectedPatch)) { + return fail( + 'VARIADIC_PASS_CONNECTED_TO_NON_VARIADIC_NODE', + { + variadicPassPinLabel, + connectedNodeType, + trace: [Patch.getPatchPath(validatedPatch)], + } + ); + } + + const connectedPatchVariadicPins = Patch.computeVariadicPins( + connectedPatch + ); + + if (Either.isLeft(connectedPatchVariadicPins)) { + // That's a job for a validator of that particular patch + return Either.of(validatedPatch); + } + + return connectedPatchVariadicPins.chain( + ({ value: variadicPinsOfConnectedNode }) => { + const inputPinKey = R.compose( + R.set(Pin.variadicPinKeySuffixLens, 0), + Link.getLinkInputPinKey + )(link); + + const isInputPinVariadic = R.compose( + R.contains(inputPinKey), + R.map(Pin.getPinKey) + )(variadicPinsOfConnectedNode); + + if (isInputPinVariadic) return Either.of(validatedPatch); + + return fail( + 'VARIADIC_PASS_CONNECTED_TO_NON_VARIADIC_PIN', + { + variadicPassPinLabel, + connectedNodeType, + trace: [Patch.getPatchPath(validatedPatch)], + } + ); + } + ); + } + ), + R.chain(connectedNode => { + const connectedNodeType = Node.getNodeType(connectedNode); + const maybeConnectedPatch = getPatchByPath( + connectedNodeType, + project + ); + + return R.sequence(Maybe.of, [ + Maybe.of(connectedNodeType), + maybeConnectedPatch, + ]); + }), + Patch.getNodeById(R.__, validatedPatch), + Link.getLinkInputNodeId + )(link); + }), + R.filter( + R.pipe(Link.getLinkOutputNodeId, R.has(R.__, variadicPassPinsByKey)) + ), + Patch.listLinks + )(validatedPatch); + } + ); + } +); + /** * Checks `patch` content to be valid: * @@ -756,7 +868,7 @@ export const validatePatchContents = def( .chain(Patch.validateAbstractPatch) .chain(Patch.validateConstructorPatch) .chain(Patch.validateRecordPatch) - .chain(Patch.validatePatchForVariadics) + .chain(validatePatchForVariadics(project)) .chain(Patch.validateBuses) ); diff --git a/packages/xod-project/test/expandVariadicNodes.spec.js b/packages/xod-project/test/expandVariadicNodes.spec.js index fe8ad40f6..93c8ea6cd 100644 --- a/packages/xod-project/test/expandVariadicNodes.spec.js +++ b/packages/xod-project/test/expandVariadicNodes.spec.js @@ -1,110 +1,54 @@ -import R from 'ramda'; import { assert } from 'chai'; -import { loadXodball } from './helpers'; +import * as H from './helpers'; +import * as XP from '../src'; -import * as Node from '../src/node'; -import * as Link from '../src/link'; -import * as Patch from '../src/patch'; -import * as Project from '../src/project'; +// assume that nodes have an unique combination of +// type, label and position +const calculateNodeIdForStructuralComparison = node => { + const type = XP.getNodeType(node); + const label = XP.getNodeLabel(node); + const position = XP.getNodePosition(node); -import expandVariadicNodes from '../src/expandVariadicNodes'; + return `${type}~~~${label}~~~${position.x}_${position.y}`; +}; describe('expandVariadicNodes', () => { it('expands a simple variadic patch', () => { - const project = loadXodball('./fixtures/expanding.xodball'); - const expandedProject = expandVariadicNodes('@/main', project); + const project = H.loadXodball('./fixtures/expanding.xodball'); + const expandedProject = XP.expandVariadicNodes('@/main', project); assert.deepEqual( - Project.getPatchByPathUnsafe('@/my-variadic', expandedProject), - Project.getPatchByPathUnsafe('@/my-variadic', project), + XP.getPatchByPathUnsafe('@/my-variadic', expandedProject), + XP.getPatchByPathUnsafe('@/my-variadic', project), 'expanded patch should not change' ); - const expected = loadXodball('./fixtures/expanding.expected.xodball'); + const expected = H.loadXodball('./fixtures/expanding.expected.xodball'); assert.sameMembers( - Project.listPatchPaths(expandedProject), - Project.listPatchPaths(expected) + XP.listPatchPaths(expandedProject), + XP.listPatchPaths(expected) ); assert.deepEqual( - Project.getPatchByPathUnsafe('@/main', expandedProject), - Project.getPatchByPathUnsafe('@/main', expected), + XP.getPatchByPathUnsafe('@/main', expandedProject), + XP.getPatchByPathUnsafe('@/main', expected), 'expanded node type should be updated' ); - const expandedPatch = Project.getPatchByPathUnsafe( + const expandedPatch = XP.getPatchByPathUnsafe( '@/my-variadic-$5', expandedProject ); - const expectedExpandedPatch = Project.getPatchByPathUnsafe( + const expectedExpandedPatch = XP.getPatchByPathUnsafe( '@/my-variadic-$5', expected ); - assert.deepEqual( - Patch.listPins(expandedPatch), - Patch.listPins(expectedExpandedPatch), - 'pins are equal' - ); - - const [ - expandedPatchNonTerminalNodes, - expectedExpandedPatchNonTerminalNodes, - ] = R.map( - R.compose( - R.sortBy(R.pipe(Node.getNodePosition, R.prop('x'))), - R.reject(Node.isPinNode), - Patch.listNodes - ), - [expandedPatch, expectedExpandedPatch] - ); - - const omitIds = R.map(R.dissoc('id')); - - assert.deepEqual( - omitIds(expandedPatchNonTerminalNodes), - omitIds(expectedExpandedPatchNonTerminalNodes), - 'non-terminal nodes are structurally equal' - ); - - // because nodes are structurally equal, - // we can compare links by replacing node ids - const correspondingExpectedNodeIds = R.compose( - R.fromPairs, - R.map(R.map(Node.getNodeId)), - R.zip - )(expandedPatchNonTerminalNodes, expectedExpandedPatchNonTerminalNodes); - const replaceId = id => correspondingExpectedNodeIds[id] || id; - - const expectedExpandedPatchLinks = R.compose(omitIds, Patch.listLinks)( + H.assertPatchesAreStructurallyEqual( + calculateNodeIdForStructuralComparison, + expandedPatch, expectedExpandedPatch ); - - const expandedPatchLinks = R.compose( - omitIds, - R.map(link => { - const inputPinKey = Link.getLinkInputPinKey(link); - const inputNodeId = R.compose(replaceId, Link.getLinkInputNodeId)(link); - const outputPinKey = Link.getLinkOutputPinKey(link); - const outputNodeId = R.compose(replaceId, Link.getLinkOutputNodeId)( - link - ); - - return Link.createLink( - inputPinKey, - inputNodeId, - outputPinKey, - outputNodeId - ); - }), - Patch.listLinks - )(expandedPatch); - - assert.deepEqual( - expandedPatchLinks, - expectedExpandedPatchLinks, - 'links are structurally equal' - ); }); }); diff --git a/packages/xod-project/test/expandVariadicPassNodes.spec.js b/packages/xod-project/test/expandVariadicPassNodes.spec.js new file mode 100644 index 000000000..eee0f2103 --- /dev/null +++ b/packages/xod-project/test/expandVariadicPassNodes.spec.js @@ -0,0 +1,52 @@ +import { assert } from 'chai'; +import * as H from './helpers'; +import * as XP from '../src'; + +// assume that nodes have an unique combination of +// type, label and position +const calculateNodeIdForStructuralComparison = node => { + const type = XP.getNodeType(node); + const label = XP.getNodeLabel(node); + const position = XP.getNodePosition(node); + + return `${type}~~~${label}~~~${position.x}_${position.y}`; +}; + +describe('expandVariadicPassNodes', () => { + it('expands a variadic-pass patch', () => { + const project = H.loadXodball('./fixtures/expanding-variadic-pass.xodball'); + const expandedProject = XP.expandVariadicPassNodes('@/main', project); + + assert.deepEqual( + XP.getPatchByPathUnsafe('@/my-variadic-pass', expandedProject), + XP.getPatchByPathUnsafe('@/my-variadic-pass', project), + 'expanded patch should not change' + ); + + const expected = H.loadXodball( + './fixtures/expanding-variadic-pass.expected.xodball' + ); + + assert.sameMembers( + XP.listPatchPaths(expandedProject), + XP.listPatchPaths(expected) + ); + + assert.deepEqual( + XP.getPatchByPathUnsafe('@/main', expandedProject), + XP.getPatchByPathUnsafe('@/main', expected), + 'expanded node type should be updated' + ); + + H.assertPatchesAreStructurallyEqual( + calculateNodeIdForStructuralComparison, + XP.getPatchByPathUnsafe('@/my-variadic-pass-$4', expected), + XP.getPatchByPathUnsafe('@/my-variadic-pass-$4', expected) + ); + H.assertPatchesAreStructurallyEqual( + calculateNodeIdForStructuralComparison, + XP.getPatchByPathUnsafe('@/my-nested-variadic-pass-$4', expected), + XP.getPatchByPathUnsafe('@/my-nested-variadic-pass-$4', expected) + ); + }); +}); diff --git a/packages/xod-project/test/fixtures/expanding-variadic-pass.expected.xodball b/packages/xod-project/test/fixtures/expanding-variadic-pass.expected.xodball new file mode 100644 index 000000000..e08d1c8d8 --- /dev/null +++ b/packages/xod-project/test/fixtures/expanding-variadic-pass.expected.xodball @@ -0,0 +1,679 @@ +{ + "patches": { + "@/main": { + "nodes": { + "HJBtpIRzu": { + "id": "HJBtpIRzu", + "type": "@/my-variadic-pass-$4", + "position": { + "x": 1, + "y": 1, + "units": "slots" + }, + "boundLiterals": { + "Hyo8pL0zu": "11", + "SkTITICfO": "12", + "Hyo8pL0zu-$1": "21", + "SkTITICfO-$1": "22", + "Hyo8pL0zu-$2": "31", + "SkTITICfO-$2": "32", + "Hyo8pL0zu-$3": "41", + "SkTITICfO-$3": "42" + } + } + }, + "path": "@/main" + }, + "@/my-variadic-pass": { + "nodes": { + "Skq3nUAGd": { + "id": "Skq3nUAGd", + "type": "@/my-nested-variadic-pass", + "position": { + "x": 3, + "y": 3, + "units": "slots" + } + }, + "rJMIpICz_": { + "id": "rJMIpICz_", + "type": "xod/patch-nodes/variadic-pass-2", + "position": { + "x": 6, + "y": 3, + "units": "slots" + } + }, + "Hyo8pL0zu": { + "id": "Hyo8pL0zu", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 3, + "y": 1, + "units": "slots" + } + }, + "SkTITICfO": { + "id": "SkTITICfO", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 4, + "y": 1, + "units": "slots" + } + }, + "rJ3d68AzO": { + "id": "rJ3d68AzO", + "type": "xod/patch-nodes/output-number", + "position": { + "x": 3, + "y": 5, + "units": "slots" + } + } + }, + "links": { + "S1VDTURGO": { + "id": "S1VDTURGO", + "output": { + "nodeId": "Hyo8pL0zu", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "Hk5io80G_" + } + }, + "SyHPTIRf_": { + "id": "SyHPTIRf_", + "output": { + "nodeId": "SkTITICfO", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "HkH6nIAf_" + } + }, + "ByRuT8CfO": { + "id": "ByRuT8CfO", + "output": { + "nodeId": "Skq3nUAGd", + "pinKey": "ry-bhIRfu" + }, + "input": { + "nodeId": "rJ3d68AzO", + "pinKey": "__in__" + } + } + }, + "path": "@/my-variadic-pass" + }, + "@/my-nested-variadic-pass": { + "nodes": { + "BJYOjU0fd": { + "id": "BJYOjU0fd", + "type": "@/foo", + "position": { + "x": 0, + "y": 2, + "units": "slots" + }, + "boundLiterals": { + "H10LiLCGu": "42", + "HJzkaICfu": "1337" + }, + "arityLevel": 2 + }, + "Hk5io80G_": { + "id": "Hk5io80G_", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 3, + "y": 0, + "units": "slots" + } + }, + "ry-bhIRfu": { + "id": "ry-bhIRfu", + "type": "xod/patch-nodes/output-number", + "position": { + "x": 0, + "y": 4, + "units": "slots" + } + }, + "HkH6nIAf_": { + "id": "HkH6nIAf_", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 4, + "y": 0, + "units": "slots" + } + }, + "rkOfaICMO": { + "id": "rkOfaICMO", + "type": "xod/patch-nodes/variadic-pass-2", + "position": { + "x": 7, + "y": 2, + "units": "slots" + } + } + }, + "links": { + "Sy_W2UAMO": { + "id": "Sy_W2UAMO", + "output": { + "nodeId": "BJYOjU0fd", + "pinKey": "rk7UiIRf_" + }, + "input": { + "nodeId": "ry-bhIRfu", + "pinKey": "__in__" + } + }, + "Skf7aUAG_": { + "id": "Skf7aUAG_", + "output": { + "nodeId": "Hk5io80G_", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "HJzkaICfu-$1" + } + }, + "BkmQTU0f_": { + "id": "BkmQTU0f_", + "output": { + "nodeId": "HkH6nIAf_", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "H10LiLCGu-$1" + } + } + }, + "path": "@/my-nested-variadic-pass" + }, + "@/foo": { + "nodes": { + "SyjVsIRfO": { + "id": "SyjVsIRfO", + "type": "xod/patch-nodes/utility", + "position": { + "x": 8, + "y": 3, + "units": "slots" + } + }, + "B1vrs8AzO": { + "id": "B1vrs8AzO", + "type": "xod/patch-nodes/not-implemented-in-xod", + "position": { + "x": 1, + "y": 3, + "units": "slots" + } + }, + "BJTSsU0Mu": { + "id": "BJTSsU0Mu", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 1, + "y": 1, + "units": "slots" + }, + "label": "ACC" + }, + "rk7UiIRf_": { + "id": "rk7UiIRf_", + "type": "xod/patch-nodes/output-number", + "position": { + "x": 1, + "y": 5, + "units": "slots" + } + }, + "H10LiLCGu": { + "id": "H10LiLCGu", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 4, + "y": 1, + "units": "slots" + }, + "label": "V1" + }, + "rJJypU0GO": { + "id": "rJJypU0GO", + "type": "xod/patch-nodes/variadic-2", + "position": { + "x": 5, + "y": 3, + "units": "slots" + } + }, + "HJzkaICfu": { + "id": "HJzkaICfu", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 7, + "y": 1, + "units": "slots" + }, + "label": "V2" + } + }, + "path": "@/foo", + "attachments": [ + { + "filename": "patch.cpp", + "encoding": "utf-8", + "content": "\nnode {\n void evaluate(Context ctx) {}\n}\n" + } + ] + }, + "@/my-variadic-pass-$4": { + "nodes": { + "Skq3nUAGd": { + "id": "Skq3nUAGd", + "type": "@/my-nested-variadic-pass-$4", + "position": { + "x": 3, + "y": 3, + "units": "slots" + } + }, + "Hyo8pL0zu": { + "id": "Hyo8pL0zu", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 3, + "y": 1, + "units": "slots" + } + }, + "SkTITICfO": { + "id": "SkTITICfO", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 4, + "y": 1, + "units": "slots" + } + }, + "rJ3d68AzO": { + "id": "rJ3d68AzO", + "type": "xod/patch-nodes/output-number", + "position": { + "x": 3, + "y": 5, + "units": "slots" + } + }, + "Hyo8pL0zu-$1": { + "id": "Hyo8pL0zu-$1", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 5, + "y": 0, + "units": "slots" + } + }, + "SkTITICfO-$1": { + "id": "SkTITICfO-$1", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 6, + "y": 0, + "units": "slots" + } + }, + "Hyo8pL0zu-$2": { + "id": "Hyo8pL0zu-$2", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 7, + "y": 0, + "units": "slots" + } + }, + "SkTITICfO-$2": { + "id": "SkTITICfO-$2", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 8, + "y": 0, + "units": "slots" + } + }, + "Hyo8pL0zu-$3": { + "id": "Hyo8pL0zu-$3", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 9, + "y": 0, + "units": "slots" + } + }, + "SkTITICfO-$3": { + "id": "SkTITICfO-$3", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 10, + "y": 0, + "units": "slots" + } + } + }, + "links": { + "S1VDTURGO": { + "id": "S1VDTURGO", + "output": { + "nodeId": "Hyo8pL0zu", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "Hk5io80G_" + } + }, + "SyHPTIRf_": { + "id": "SyHPTIRf_", + "output": { + "nodeId": "SkTITICfO", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "HkH6nIAf_" + } + }, + "ByRuT8CfO": { + "id": "ByRuT8CfO", + "output": { + "nodeId": "Skq3nUAGd", + "pinKey": "ry-bhIRfu" + }, + "input": { + "nodeId": "rJ3d68AzO", + "pinKey": "__in__" + } + }, + "BywLCLCfO": { + "id": "BywLCLCfO", + "output": { + "nodeId": "Hyo8pL0zu-$1", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "Hk5io80G_-$1" + } + }, + "BklD8CL0zd": { + "id": "BklD8CL0zd", + "output": { + "nodeId": "SkTITICfO-$1", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "HkH6nIAf_-$1" + } + }, + "ryWPI0LCzu": { + "id": "ryWPI0LCzu", + "output": { + "nodeId": "Hyo8pL0zu-$2", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "Hk5io80G_-$2" + } + }, + "SJzDL0UAfu": { + "id": "SJzDL0UAfu", + "output": { + "nodeId": "SkTITICfO-$2", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "HkH6nIAf_-$2" + } + }, + "HyQvLA8Rfd": { + "id": "HyQvLA8Rfd", + "output": { + "nodeId": "Hyo8pL0zu-$3", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "Hk5io80G_-$3" + } + }, + "H1Ew808AGO": { + "id": "H1Ew808AGO", + "output": { + "nodeId": "SkTITICfO-$3", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "HkH6nIAf_-$3" + } + } + }, + "path": "@/my-variadic-pass-$4" + }, + "@/my-nested-variadic-pass-$4": { + "nodes": { + "BJYOjU0fd": { + "id": "BJYOjU0fd", + "type": "@/foo", + "position": { + "x": 0, + "y": 2, + "units": "slots" + }, + "boundLiterals": { + "H10LiLCGu": "42", + "HJzkaICfu": "1337" + }, + "arityLevel": 5 + }, + "Hk5io80G_": { + "id": "Hk5io80G_", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 3, + "y": 0, + "units": "slots" + } + }, + "ry-bhIRfu": { + "id": "ry-bhIRfu", + "type": "xod/patch-nodes/output-number", + "position": { + "x": 0, + "y": 4, + "units": "slots" + } + }, + "HkH6nIAf_": { + "id": "HkH6nIAf_", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 4, + "y": 0, + "units": "slots" + } + }, + "Hk5io80G_-$1": { + "id": "Hk5io80G_-$1", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 5, + "y": 0, + "units": "slots" + } + }, + "HkH6nIAf_-$1": { + "id": "HkH6nIAf_-$1", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 6, + "y": 0, + "units": "slots" + } + }, + "Hk5io80G_-$2": { + "id": "Hk5io80G_-$2", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 7, + "y": 0, + "units": "slots" + } + }, + "HkH6nIAf_-$2": { + "id": "HkH6nIAf_-$2", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 8, + "y": 0, + "units": "slots" + } + }, + "Hk5io80G_-$3": { + "id": "Hk5io80G_-$3", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 9, + "y": 0, + "units": "slots" + } + }, + "HkH6nIAf_-$3": { + "id": "HkH6nIAf_-$3", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 10, + "y": 0, + "units": "slots" + } + } + }, + "links": { + "Sy_W2UAMO": { + "id": "Sy_W2UAMO", + "output": { + "nodeId": "BJYOjU0fd", + "pinKey": "rk7UiIRf_" + }, + "input": { + "nodeId": "ry-bhIRfu", + "pinKey": "__in__" + } + }, + "Skf7aUAG_": { + "id": "Skf7aUAG_", + "output": { + "nodeId": "Hk5io80G_", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "HJzkaICfu-$1" + } + }, + "BkmQTU0f_": { + "id": "BkmQTU0f_", + "output": { + "nodeId": "HkH6nIAf_", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "H10LiLCGu-$1" + } + }, + "SJHPU0LAzO": { + "id": "SJHPU0LAzO", + "output": { + "nodeId": "Hk5io80G_-$1", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "HJzkaICfu-$2" + } + }, + "Sy8w8RUAGu": { + "id": "Sy8w8RUAGu", + "output": { + "nodeId": "HkH6nIAf_-$1", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "H10LiLCGu-$2" + } + }, + "HyDvIAI0Gu": { + "id": "HyDvIAI0Gu", + "output": { + "nodeId": "Hk5io80G_-$2", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "HJzkaICfu-$3" + } + }, + "H1uP8AURfu": { + "id": "H1uP8AURfu", + "output": { + "nodeId": "HkH6nIAf_-$2", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "H10LiLCGu-$3" + } + }, + "S1KvURIRGO": { + "id": "S1KvURIRGO", + "output": { + "nodeId": "Hk5io80G_-$3", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "HJzkaICfu-$4" + } + }, + "S1cw80LRG_": { + "id": "S1cw80LRG_", + "output": { + "nodeId": "HkH6nIAf_-$3", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "H10LiLCGu-$4" + } + } + }, + "path": "@/my-nested-variadic-pass-$4" + } + }, + "name": "" +} \ No newline at end of file diff --git a/packages/xod-project/test/fixtures/expanding-variadic-pass.xodball b/packages/xod-project/test/fixtures/expanding-variadic-pass.xodball new file mode 100644 index 000000000..35c18fe50 --- /dev/null +++ b/packages/xod-project/test/fixtures/expanding-variadic-pass.xodball @@ -0,0 +1,283 @@ +{ + "patches": { + "@/main": { + "nodes": { + "HJBtpIRzu": { + "id": "HJBtpIRzu", + "type": "@/my-variadic-pass", + "position": { + "x": 1, + "y": 1, + "units": "slots" + }, + "boundLiterals": { + "Hyo8pL0zu": "11", + "SkTITICfO": "12", + "Hyo8pL0zu-$1": "21", + "SkTITICfO-$1": "22", + "Hyo8pL0zu-$2": "31", + "SkTITICfO-$2": "32", + "Hyo8pL0zu-$3": "41", + "SkTITICfO-$3": "42" + }, + "arityLevel": 4 + } + }, + "path": "@/main" + }, + "@/my-variadic-pass": { + "nodes": { + "Skq3nUAGd": { + "id": "Skq3nUAGd", + "type": "@/my-nested-variadic-pass", + "position": { + "x": 3, + "y": 3, + "units": "slots" + } + }, + "rJMIpICz_": { + "id": "rJMIpICz_", + "type": "xod/patch-nodes/variadic-pass-2", + "position": { + "x": 6, + "y": 3, + "units": "slots" + } + }, + "Hyo8pL0zu": { + "id": "Hyo8pL0zu", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 3, + "y": 1, + "units": "slots" + } + }, + "SkTITICfO": { + "id": "SkTITICfO", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 4, + "y": 1, + "units": "slots" + } + }, + "rJ3d68AzO": { + "id": "rJ3d68AzO", + "type": "xod/patch-nodes/output-number", + "position": { + "x": 3, + "y": 5, + "units": "slots" + } + } + }, + "links": { + "S1VDTURGO": { + "id": "S1VDTURGO", + "output": { + "nodeId": "Hyo8pL0zu", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "Hk5io80G_" + } + }, + "SyHPTIRf_": { + "id": "SyHPTIRf_", + "output": { + "nodeId": "SkTITICfO", + "pinKey": "__out__" + }, + "input": { + "nodeId": "Skq3nUAGd", + "pinKey": "HkH6nIAf_" + } + }, + "ByRuT8CfO": { + "id": "ByRuT8CfO", + "output": { + "nodeId": "Skq3nUAGd", + "pinKey": "ry-bhIRfu" + }, + "input": { + "nodeId": "rJ3d68AzO", + "pinKey": "__in__" + } + } + }, + "path": "@/my-variadic-pass" + }, + "@/my-nested-variadic-pass": { + "nodes": { + "BJYOjU0fd": { + "id": "BJYOjU0fd", + "type": "@/foo", + "position": { + "x": 0, + "y": 2, + "units": "slots" + }, + "boundLiterals": { + "H10LiLCGu": "42", + "HJzkaICfu": "1337" + }, + "arityLevel": 2 + }, + "Hk5io80G_": { + "id": "Hk5io80G_", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 3, + "y": 0, + "units": "slots" + } + }, + "ry-bhIRfu": { + "id": "ry-bhIRfu", + "type": "xod/patch-nodes/output-number", + "position": { + "x": 0, + "y": 4, + "units": "slots" + } + }, + "HkH6nIAf_": { + "id": "HkH6nIAf_", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 4, + "y": 0, + "units": "slots" + } + }, + "rkOfaICMO": { + "id": "rkOfaICMO", + "type": "xod/patch-nodes/variadic-pass-2", + "position": { + "x": 7, + "y": 2, + "units": "slots" + } + } + }, + "links": { + "Sy_W2UAMO": { + "id": "Sy_W2UAMO", + "output": { + "nodeId": "BJYOjU0fd", + "pinKey": "rk7UiIRf_" + }, + "input": { + "nodeId": "ry-bhIRfu", + "pinKey": "__in__" + } + }, + "Skf7aUAG_": { + "id": "Skf7aUAG_", + "output": { + "nodeId": "Hk5io80G_", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "HJzkaICfu-$1" + } + }, + "BkmQTU0f_": { + "id": "BkmQTU0f_", + "output": { + "nodeId": "HkH6nIAf_", + "pinKey": "__out__" + }, + "input": { + "nodeId": "BJYOjU0fd", + "pinKey": "H10LiLCGu-$1" + } + } + }, + "path": "@/my-nested-variadic-pass" + }, + "@/foo": { + "nodes": { + "SyjVsIRfO": { + "id": "SyjVsIRfO", + "type": "xod/patch-nodes/utility", + "position": { + "x": 8, + "y": 3, + "units": "slots" + } + }, + "B1vrs8AzO": { + "id": "B1vrs8AzO", + "type": "xod/patch-nodes/not-implemented-in-xod", + "position": { + "x": 1, + "y": 3, + "units": "slots" + } + }, + "BJTSsU0Mu": { + "id": "BJTSsU0Mu", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 1, + "y": 1, + "units": "slots" + }, + "label": "ACC" + }, + "rk7UiIRf_": { + "id": "rk7UiIRf_", + "type": "xod/patch-nodes/output-number", + "position": { + "x": 1, + "y": 5, + "units": "slots" + } + }, + "H10LiLCGu": { + "id": "H10LiLCGu", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 4, + "y": 1, + "units": "slots" + }, + "label": "V1" + }, + "rJJypU0GO": { + "id": "rJJypU0GO", + "type": "xod/patch-nodes/variadic-2", + "position": { + "x": 5, + "y": 3, + "units": "slots" + } + }, + "HJzkaICfu": { + "id": "HJzkaICfu", + "type": "xod/patch-nodes/input-number", + "position": { + "x": 7, + "y": 1, + "units": "slots" + }, + "label": "V2" + } + }, + "path": "@/foo", + "attachments": [ + { + "filename": "patch.cpp", + "encoding": "utf-8", + "content": "\nnode {\n void evaluate(Context ctx) {}\n}\n" + } + ] + } + }, + "name": "" +} \ No newline at end of file From b17f125aa1a8e4fbf6d332140cb2cda6d45cb875 Mon Sep 17 00:00:00 2001 From: Evgeny Kochetkov Date: Thu, 4 Mar 2021 16:43:11 +0300 Subject: [PATCH 2/2] feat(stdlib): add xod/core/arity-level, xod/core/average nodes --- .../xod/core/arity-level-internal/patch.cpp | 6 + .../xod/core/arity-level-internal/patch.xodp | 58 +++++++ .../__lib__/xod/core/arity-level/patch.xodp | 65 ++++++++ workspace/__lib__/xod/core/average/patch.xodp | 116 ++++++++++++++ .../test-core/test-arity-level/patch.test.tsv | 2 + .../test-core/test-arity-level/patch.xodp | 113 ++++++++++++++ .../test-core/test-average/patch.test.tsv | 3 + .../test/test-core/test-average/patch.xodp | 145 ++++++++++++++++++ 8 files changed, 508 insertions(+) create mode 100644 workspace/__lib__/xod/core/arity-level-internal/patch.cpp create mode 100644 workspace/__lib__/xod/core/arity-level-internal/patch.xodp create mode 100644 workspace/__lib__/xod/core/arity-level/patch.xodp create mode 100644 workspace/__lib__/xod/core/average/patch.xodp create mode 100644 workspace/test/test-core/test-arity-level/patch.test.tsv create mode 100644 workspace/test/test-core/test-arity-level/patch.xodp create mode 100644 workspace/test/test-core/test-average/patch.test.tsv create mode 100644 workspace/test/test-core/test-average/patch.xodp diff --git a/workspace/__lib__/xod/core/arity-level-internal/patch.cpp b/workspace/__lib__/xod/core/arity-level-internal/patch.cpp new file mode 100644 index 000000000..366098a16 --- /dev/null +++ b/workspace/__lib__/xod/core/arity-level-internal/patch.cpp @@ -0,0 +1,6 @@ + +node { + void evaluate(Context ctx) { + emitValue(ctx, getValue(ctx) + 1); + } +} diff --git a/workspace/__lib__/xod/core/arity-level-internal/patch.xodp b/workspace/__lib__/xod/core/arity-level-internal/patch.xodp new file mode 100644 index 000000000..7102cd12a --- /dev/null +++ b/workspace/__lib__/xod/core/arity-level-internal/patch.xodp @@ -0,0 +1,58 @@ +{ + "nodes": [ + { + "id": "HJk7Ze2M_", + "position": { + "units": "slots", + "x": 4, + "y": 1 + }, + "type": "xod/patch-nodes/variadic-1" + }, + { + "id": "BJBQbx3Md", + "position": { + "units": "slots", + "x": 1, + "y": 1 + }, + "type": "xod/patch-nodes/not-implemented-in-xod" + }, + { + "id": "ry9mWx3fd", + "position": { + "units": "slots", + "x": 2, + "y": 0 + }, + "type": "xod/patch-nodes/input-t1" + }, + { + "id": "S1gVWl2Md", + "position": { + "units": "slots", + "x": 1, + "y": 0 + }, + "type": "xod/patch-nodes/input-number" + }, + { + "id": "SkLNbxnfO", + "position": { + "units": "slots", + "x": 1, + "y": 2 + }, + "type": "xod/patch-nodes/output-number" + }, + { + "id": "SJj4Ze3zO", + "position": { + "units": "slots", + "x": 5, + "y": 1 + }, + "type": "xod/patch-nodes/utility" + } + ] +} diff --git a/workspace/__lib__/xod/core/arity-level/patch.xodp b/workspace/__lib__/xod/core/arity-level/patch.xodp new file mode 100644 index 000000000..76eaf65a8 --- /dev/null +++ b/workspace/__lib__/xod/core/arity-level/patch.xodp @@ -0,0 +1,65 @@ +{ + "description": "When connected to a variadic-pass input, outputs arity level of parent node", + "links": [ + { + "id": "ry2DSx3G_", + "input": { + "nodeId": "rkKPBe2G_", + "pinKey": "ry9mWx3fd" + }, + "output": { + "nodeId": "B1DIHlhzu", + "pinKey": "__out__" + } + }, + { + "id": "r16wrx3fO", + "input": { + "nodeId": "HJTUBe3f_", + "pinKey": "__in__" + }, + "output": { + "nodeId": "rkKPBe2G_", + "pinKey": "SkLNbxnfO" + } + } + ], + "nodes": [ + { + "id": "HyCrHxnzO", + "position": { + "units": "slots", + "x": 3, + "y": 1 + }, + "type": "xod/patch-nodes/variadic-pass-1" + }, + { + "id": "B1DIHlhzu", + "position": { + "units": "slots", + "x": 1, + "y": 0 + }, + "type": "xod/patch-nodes/input-t1" + }, + { + "id": "HJTUBe3f_", + "position": { + "units": "slots", + "x": 0, + "y": 2 + }, + "type": "xod/patch-nodes/output-number" + }, + { + "id": "rkKPBe2G_", + "position": { + "units": "slots", + "x": 0, + "y": 1 + }, + "type": "@/arity-level-internal" + } + ] +} diff --git a/workspace/__lib__/xod/core/average/patch.xodp b/workspace/__lib__/xod/core/average/patch.xodp new file mode 100644 index 000000000..39ae472fb --- /dev/null +++ b/workspace/__lib__/xod/core/average/patch.xodp @@ -0,0 +1,116 @@ +{ + "description": "Adds the input values together and divides by the number of values", + "links": [ + { + "id": "Bk4hHl3Gd", + "input": { + "nodeId": "ByG2rlnfu", + "pinKey": "HkqmLAOrD1W" + }, + "output": { + "nodeId": "rJG5SlhGu", + "pinKey": "__out__" + } + }, + { + "id": "HJv3SxnMd", + "input": { + "nodeId": "S10jrlhG_", + "pinKey": "B1DIHlhzu" + }, + "output": { + "nodeId": "rJG5SlhGu", + "pinKey": "__out__" + } + }, + { + "id": "rks2Bl2Mu", + "input": { + "nodeId": "Sytnrx3Mu", + "pinKey": "SkdIRuBD1b" + }, + "output": { + "nodeId": "ByG2rlnfu", + "pinKey": "SyomIRurDJ-" + } + }, + { + "id": "SJhhSe2Gd", + "input": { + "nodeId": "Sytnrx3Mu", + "pinKey": "BytUCdHD1-" + }, + "output": { + "nodeId": "S10jrlhG_", + "pinKey": "HJTUBe3f_" + } + }, + { + "id": "BJ62He2f_", + "input": { + "nodeId": "BJgjHe3Mu", + "pinKey": "__in__" + }, + "output": { + "nodeId": "Sytnrx3Mu", + "pinKey": "BkqLCOSw1W" + } + } + ], + "nodes": [ + { + "id": "rJG5SlhGu", + "position": { + "units": "slots", + "x": 1, + "y": 0 + }, + "type": "xod/patch-nodes/input-number" + }, + { + "id": "S1wcrgnfu", + "position": { + "units": "slots", + "x": 5, + "y": 1 + }, + "type": "xod/patch-nodes/variadic-pass-1" + }, + { + "id": "BJgjHe3Mu", + "position": { + "units": "slots", + "x": 1, + "y": 3 + }, + "type": "xod/patch-nodes/output-number" + }, + { + "id": "S10jrlhG_", + "position": { + "units": "slots", + "x": 3, + "y": 1 + }, + "type": "@/arity-level" + }, + { + "id": "ByG2rlnfu", + "position": { + "units": "slots", + "x": 0, + "y": 1 + }, + "type": "@/add" + }, + { + "id": "Sytnrx3Mu", + "position": { + "units": "slots", + "x": 1, + "y": 2 + }, + "type": "@/divide" + } + ] +} diff --git a/workspace/test/test-core/test-arity-level/patch.test.tsv b/workspace/test/test-core/test-arity-level/patch.test.tsv new file mode 100644 index 000000000..12938b4b5 --- /dev/null +++ b/workspace/test/test-core/test-arity-level/patch.test.tsv @@ -0,0 +1,2 @@ +OUT1 OUT2 OUT3 +1 2 6 diff --git a/workspace/test/test-core/test-arity-level/patch.xodp b/workspace/test/test-core/test-arity-level/patch.xodp new file mode 100644 index 000000000..9635df85e --- /dev/null +++ b/workspace/test/test-core/test-arity-level/patch.xodp @@ -0,0 +1,113 @@ +{ + "links": [ + { + "id": "BJ5oSGTzO", + "input": { + "nodeId": "SytiSzTGO", + "pinKey": "__in__" + }, + "output": { + "nodeId": "HyziHf6Md", + "pinKey": "HJTUBe3f_" + } + }, + { + "id": "HyZa3rGazO", + "input": { + "nodeId": "SJeanBM6GO", + "pinKey": "__in__" + }, + "output": { + "nodeId": "HJT2SfTf_", + "pinKey": "HJTUBe3f_" + } + }, + { + "id": "BJ-fArM6MO", + "input": { + "nodeId": "H1xf0rf6zd", + "pinKey": "__in__" + }, + "output": { + "nodeId": "SyG0BMTzd", + "pinKey": "HJTUBe3f_" + } + } + ], + "nodes": [ + { + "boundLiterals": { + "B1DIHlhzu": "\"foo\"" + }, + "id": "HyziHf6Md", + "position": { + "units": "slots", + "x": 1, + "y": 1 + }, + "type": "xod/core/arity-level" + }, + { + "id": "SytiSzTGO", + "position": { + "units": "slots", + "x": 1, + "y": 2 + }, + "type": "xod/patch-nodes/output-number" + }, + { + "arityLevel": 2, + "boundLiterals": { + "B1DIHlhzu": "0" + }, + "id": "HJT2SfTf_", + "position": { + "units": "slots", + "x": 4, + "y": 1 + }, + "type": "xod/core/arity-level" + }, + { + "id": "SJeanBM6GO", + "position": { + "units": "slots", + "x": 4, + "y": 2 + }, + "type": "xod/patch-nodes/output-number" + }, + { + "arityLevel": 6, + "boundLiterals": { + "B1DIHlhzu": "True" + }, + "id": "SyG0BMTzd", + "position": { + "units": "slots", + "x": 8, + "y": 1 + }, + "type": "xod/core/arity-level" + }, + { + "id": "H1xf0rf6zd", + "position": { + "units": "slots", + "x": 8, + "y": 2 + }, + "type": "xod/patch-nodes/output-number" + }, + { + "id": "B1MbIzTzd", + "position": { + "units": "slots", + "x": 16, + "y": 1 + }, + "type": "xod/patch-nodes/tabtest" + } + ] +} diff --git a/workspace/test/test-core/test-average/patch.test.tsv b/workspace/test/test-core/test-average/patch.test.tsv new file mode 100644 index 000000000..4aeb537a8 --- /dev/null +++ b/workspace/test/test-core/test-average/patch.test.tsv @@ -0,0 +1,3 @@ +IN1 IN2 IN3 IN4 IN5 OUT +1 2 3 4 5 3 +10000 1000 100 10 1 2222.2 diff --git a/workspace/test/test-core/test-average/patch.xodp b/workspace/test/test-core/test-average/patch.xodp new file mode 100644 index 000000000..2bc31a26f --- /dev/null +++ b/workspace/test/test-core/test-average/patch.xodp @@ -0,0 +1,145 @@ +{ + "links": [ + { + "id": "ByXgSWazu", + "input": { + "nodeId": "r1Qt4WpMu", + "pinKey": "__in__" + }, + "output": { + "nodeId": "BygdVWaMu", + "pinKey": "BJgjHe3Mu" + } + }, + { + "id": "ByElSWpMO", + "input": { + "nodeId": "BygdVWaMu", + "pinKey": "rJG5SlhGu" + }, + "output": { + "nodeId": "H15uNW6fO", + "pinKey": "__out__" + } + }, + { + "id": "HySgrbpfu", + "input": { + "nodeId": "BygdVWaMu", + "pinKey": "rJG5SlhGu-$1" + }, + "output": { + "nodeId": "rkndEbaM_", + "pinKey": "__out__" + } + }, + { + "id": "rknSgSWTGO", + "input": { + "nodeId": "BygdVWaMu", + "pinKey": "rJG5SlhGu-$2" + }, + "output": { + "nodeId": "HJh2dN-6f_", + "pinKey": "__out__" + } + }, + { + "id": "BkLxHZTzd", + "input": { + "nodeId": "BygdVWaMu", + "pinKey": "rJG5SlhGu-$3" + }, + "output": { + "nodeId": "H1Kx3_NZTfu", + "pinKey": "__out__" + } + }, + { + "id": "BJwlSbTzu", + "input": { + "nodeId": "BygdVWaMu", + "pinKey": "rJG5SlhGu-$4" + }, + "output": { + "nodeId": "B1LZ2ONWpz_", + "pinKey": "__out__" + } + } + ], + "nodes": [ + { + "arityLevel": 5, + "id": "BygdVWaMu", + "position": { + "units": "slots", + "x": 1, + "y": 1 + }, + "type": "xod/core/average" + }, + { + "id": "H15uNW6fO", + "position": { + "units": "slots", + "x": 1, + "y": 0 + }, + "type": "xod/patch-nodes/input-number" + }, + { + "id": "rkndEbaM_", + "position": { + "units": "slots", + "x": 2, + "y": 0 + }, + "type": "xod/patch-nodes/input-number" + }, + { + "id": "HJh2dN-6f_", + "position": { + "units": "slots", + "x": 3, + "y": 0 + }, + "type": "xod/patch-nodes/input-number" + }, + { + "id": "H1Kx3_NZTfu", + "position": { + "units": "slots", + "x": 4, + "y": 0 + }, + "type": "xod/patch-nodes/input-number" + }, + { + "id": "B1LZ2ONWpz_", + "position": { + "units": "slots", + "x": 5, + "y": 0 + }, + "type": "xod/patch-nodes/input-number" + }, + { + "id": "r1Qt4WpMu", + "position": { + "units": "slots", + "x": 1, + "y": 2 + }, + "type": "xod/patch-nodes/output-number" + }, + { + "id": "BJTeBb6zO", + "position": { + "units": "slots", + "x": 7, + "y": 1 + }, + "type": "xod/patch-nodes/tabtest" + } + ] +}