mahler
Version:
A automated task composer and HTN based planner for building autonomous system agents
127 lines • 4.88 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Distance = void 0;
exports.diff = diff;
const pointer_1 = require("./pointer");
const path_1 = require("./path");
const target_1 = require("./target");
const utils_1 = require("./utils");
function applyPatch(s, t) {
if (t != null && !Array.isArray(t) && typeof t === 'object') {
const result = { ...s };
for (const [key, value] of Object.entries(t)) {
if (value === target_1.UNDEFINED) {
delete result[key];
}
else {
result[key] = applyPatch(result[key], value);
}
}
return result;
}
return t;
}
/**
* getOperations returns all the possible operations that are applicable given a
* new target state. This means, for instance, that if a target state creates a new
* value under /a/b/c, this function must report a 'create' operation on that path, but also
* 'update' operations on '/a/b', '/a', and '/' as any of those can result in same outcome
*/
function* getOperations(s, t) {
// We store target, path pair in a quee so we can visit the full target
// object, ordered by level, without recursion
const queue = [
{ tgt: t, ref: [] },
];
// The target object
const patched = applyPatch(s, t);
// The list of operations to return
while (queue.length > 0) {
const { tgt, ref, isLeaf } = queue.shift();
const path = path_1.Path.from(ref);
const sValue = pointer_1.Pointer.from(s, path);
const tValue = tgt !== target_1.UNDEFINED ? pointer_1.Pointer.from(patched, path) : undefined;
// If the target is DELETED, and the source value still
// exists we need to add a delete operation
if (tgt === target_1.UNDEFINED && sValue != null) {
yield { op: 'delete', path, isLeaf: isLeaf == null };
}
else if (tgt !== target_1.UNDEFINED) {
// If the source value does not exist, then we add a `create`
// operation
if (sValue == null) {
yield { op: 'create', path, target: tValue, isLeaf: true };
}
// If the source value does exist, we do a deep comparison compare the source to the patched
// version and if they don't match, we add an `update` operation
else if (!(0, utils_1.deepEqual)(sValue, tValue)) {
yield {
op: 'update',
path,
source: sValue,
target: tValue,
isLeaf:
// If the source or target are not objects, or they are arrays, then
// we wont continue recursing so the object is a leaf
typeof sValue !== 'object' ||
Array.isArray(sValue) ||
typeof tValue !== 'object' ||
Array.isArray(tValue),
};
}
}
if (tgt != null &&
!Array.isArray(tgt) &&
typeof tgt === 'object' &&
// Only expand the target if the source exists
sValue != null) {
// Add target keys to stack to recurse
for (const key of Object.keys(tgt)) {
const value = tgt[key];
const newPath = ref.concat(key);
queue.push({ tgt: value, ref: newPath });
}
}
// if tgt is DELETE, then we should also add rules to delete the current
// properties recursively
if (tgt === target_1.UNDEFINED &&
sValue != null &&
!Array.isArray(sValue) &&
typeof sValue === 'object') {
for (const key of Object.keys(sValue)) {
const newPath = ref.concat(key);
// We set isLeaf to false here because we know that the source
// comes from a previous iteration
queue.push({ tgt: target_1.UNDEFINED, ref: newPath, isLeaf: false });
}
}
}
}
/**
* Calculates the list of changes between the current state and the target
*
* Returns only the leaf operations.
*/
function diff(s, t) {
const ops = [...getOperations(s, t)];
return ops.filter(({ isLeaf }) => isLeaf).map(({ isLeaf, ...op }) => op);
}
function from(src, tgt) {
const target = applyPatch(src, tgt);
return Object.assign((s) => {
// NOTE: we return an array here, but we could easily
// return an iterator instead for better memory usage
return [...getOperations(s, tgt)].map(({ isLeaf, ...op }) => {
delete op.source;
return op;
});
}, {
get target() {
return target;
},
});
}
exports.Distance = {
from,
};
//# sourceMappingURL=distance.js.map