jinaga
Version:
Data management for web and mobile applications.
235 lines • 9.88 kB
JavaScript
"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