UNPKG

jinaga

Version:

Data management for web and mobile applications.

235 lines 9.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.invertSpecification = void 0; ; function invertSpecification(specification) { // Turn each given into a match. const emptyMatches = specification.given.map(g => ({ unknown: g, conditions: [] })); const matches = [...emptyMatches, ...specification.matches]; const labels = specification.matches.map(m => m.unknown); const givenSubset = specification.given.map(g => g.name); const resultSubset = [...givenSubset, ...labels.map(l => l.name)]; const context = { path: "", givenSubset, parentSubset: givenSubset, resultSubset, projection: specification.projection }; const inverses = invertMatches(matches, labels, context); const projectionInverses = invertProjection(matches, context); return [...inverses, ...projectionInverses]; } exports.invertSpecification = invertSpecification; function invertMatches(matches, labels, context) { const inverses = []; // Produce an inverse for each unknown in the original specification. for (const label of labels) { matches = shakeTree(matches, label.name); // The given will not have any successors. // Simplify the matches by removing any conditions that cannot be satisfied. const simplified = simplifyMatches(matches, label.name); if (simplified !== null) { const inverseSpecification = { given: [label], matches: simplified.slice(1), projection: context.projection }; const inverse = { inverseSpecification, operation: "add", givenSubset: context.givenSubset, parentSubset: context.parentSubset, path: context.path, resultSubset: context.resultSubset }; inverses.push(inverse); } const existentialInverses = invertExistentialConditions(matches, matches[0].conditions, "add", context); inverses.push(...existentialInverses); } return inverses; } function shakeTree(matches, label) { // Find the match for the given label. const match = findMatch(matches, label); // Move the match to the beginning of the list. matches = [match, ...matches.filter(m => m !== match)]; // Invert all path conditions in the match and move them to the tagged match. for (const condition of match.conditions) { if (condition.type === "path") { matches = invertAndMovePathCondition(matches, label, condition); } } // Move any other matches with no paths down. for (let i = 1; i < matches.length; i++) { let otherMatch = matches[i]; while (!otherMatch.conditions.some(c => c.type === "path")) { // Find all matches beyond this point that tag this one. for (let j = i + 1; j < matches.length; j++) { const taggedMatch = matches[j]; // Move their path conditions to the other match. for (const taggedCondition of taggedMatch.conditions) { if (taggedCondition.type === "path" && taggedCondition.labelRight === otherMatch.unknown.name) { matches = invertAndMovePathCondition(matches, taggedMatch.unknown.name, taggedCondition); } } } // Move the other match to the bottom of the list. matches = [...matches.slice(0, i), ...matches.slice(i + 1), matches[i]]; otherMatch = matches[i]; } } return matches; } function invertAndMovePathCondition(matches, label, pathCondition) { // Find the match for the given label. const match = findMatch(matches, label); // Find the match for the target label. const targetMatch = findMatch(matches, pathCondition.labelRight); // Invert the path condition. const invertedPathCondition = { type: "path", labelRight: match.unknown.name, rolesRight: pathCondition.rolesLeft, rolesLeft: pathCondition.rolesRight }; // Remove the path condition from the match. const newMatch = { unknown: match.unknown, conditions: match.conditions.filter(c => c !== pathCondition) }; const matchIndex = matches.indexOf(match); matches = [...matches.slice(0, matchIndex), newMatch, ...matches.slice(matchIndex + 1)]; // Add the inverted path condition to the target match. const newTargetMatch = { unknown: targetMatch.unknown, conditions: [invertedPathCondition, ...targetMatch.conditions] }; const targetMatchIndex = matches.indexOf(targetMatch); matches = [...matches.slice(0, targetMatchIndex), newTargetMatch, ...matches.slice(targetMatchIndex + 1)]; return matches; } function findMatch(matches, label) { for (const match of matches) { if (match.unknown.name === label) { return match; } } throw new Error(`Label ${label} not found`); } function invertExistentialConditions(outerMatches, conditions, parentOperation, context) { const inverses = []; // Produce inverses for each existential condition in the match. for (const condition of conditions) { if (condition.type === "existential") { let matches = [...outerMatches, ...condition.matches]; for (const match of condition.matches) { matches = shakeTree(matches, match.unknown.name); const matchesWithoutCondition = removeCondition(matches.slice(1), condition); const inverseSpecification = { given: [match.unknown], matches: matchesWithoutCondition, projection: context.projection }; const operation = inferOperation(parentOperation, condition.exists); const inverse = { inverseSpecification, operation, givenSubset: context.givenSubset, parentSubset: context.parentSubset, path: context.path, resultSubset: context.resultSubset }; inverses.push(inverse); const existentialInverses = invertExistentialConditions(matches, match.conditions, operation, context); inverses.push(...existentialInverses); } } } return inverses; } function removeCondition(matches, condition) { return matches.map(match => match.conditions.includes(condition) ? { unknown: match.unknown, conditions: match.conditions.filter(c => c !== condition) } : match); } function inferOperation(parentOperation, exists) { if (parentOperation === "add") { return exists ? "add" : "remove"; } else if (parentOperation === "remove") { return exists ? "remove" : "add"; } else { const _exhaustiveCheck = parentOperation; throw new Error(`Cannot infer operation from ${_exhaustiveCheck}, ${exists ? "exists" : "not exists"}`); } } function invertProjection(matches, context) { const inverses = []; // Produce inverses for all collections in the projection. if (context.projection.type === "composite") { for (const component of context.projection.components) { if (component.type === "specification") { const componentMatches = [...matches, ...component.matches]; const componentLabels = component.matches.map(m => m.unknown); const childContext = Object.assign(Object.assign({}, context), { path: context.path + "." + component.name, parentSubset: context.resultSubset, resultSubset: [...context.resultSubset, ...componentLabels.map(l => l.name)], projection: component.projection }); const matchInverses = invertMatches(componentMatches, componentLabels, childContext); const projectionInverses = invertProjection(componentMatches, childContext); inverses.push(...matchInverses, ...projectionInverses); } } } return inverses; } function simplifyMatches(matches, given) { const simplifiedMatches = []; for (const match of matches) { const simplifiedMatch = simplifyMatch(match, given); if (simplifiedMatch === null) { return null; } else { simplifiedMatches.push(simplifiedMatch); } } return simplifiedMatches; } function simplifyMatch(match, given) { const simplifiedConditions = []; for (const condition of match.conditions) { if (expectsSuccessor(condition, given)) { // This path condition matches successors of the given. // There are no successors yet, so the condition is unsatisfiable. return null; } if (condition.type === "existential") { const anyExpectsSuccessor = condition.matches.some(m => m.conditions.some(c => expectsSuccessor(c, given))); if (anyExpectsSuccessor && condition.exists) { // This existential condition expects successors of the given. // There are no successors yet, so the condition is unsatisfiable. return null; } } simplifiedConditions.push(condition); } return { unknown: match.unknown, conditions: simplifiedConditions }; } function expectsSuccessor(condition, given) { return condition.type === "path" && condition.labelRight === given && condition.rolesRight.length === 0 && condition.rolesLeft.length > 0; } //# sourceMappingURL=inverse.js.map