diff --git a/packages/xod-arduino/src/transpiler.js b/packages/xod-arduino/src/transpiler.js index 9e776b26..2743dc70 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 b144bea6..0fec0219 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 ad2a2a85..a97fff83 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 baa60a92..70b2744c 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 00000000..05a29066 --- /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 c9985c17..21de5320 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 09e66919..cf0a12b1 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 e6798d3b..3323fc59 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 (!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, - }); + 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 6c80adf7..5a59aa75 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 eeed3376..c089b841 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 ccea5dc1..543065a4 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 fe8ad40f..93c8ea6c 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 00000000..eee0f210 --- /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 00000000..e08d1c8d --- /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 00000000..35c18fe5 --- /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