Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion common/vendor-libs/patch-steps-lib.d.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
import {appliers, DebugState} from "./patchsteps-patch.js";

/**
* @typedef State
* @property {unknown} currentValue
* @property {unknown[]} stack
* @property {(fromGame: boolean| string, path: string) => Promise<any>}
* @property {DebugState} debugState
* @property {boolean} debug
* /
export interface State {
currentValue: unknown,
stack: unknown[],
debugState: DebugState,
debug: boolean
}

/**
* A user defined step that is distinguishable from builtin PatchSteps.
* Errors that occur in callables are not handled by the PatchSteps interpreter.
*
* @async
* @callback Callable
* @param {State} state is the internal PatchStep state.
* @param {unknown} args is the user supplied arguments.
*/
export type Callable = (state: State, args: unknown) => Promise<void>;

/* @type {Map<string,Callable>} */
const callables = new Map;
const callables = new Map<string, Callable>();

/**
* @param {string} id
* @param {Callable} callable
*/
export function register(id, callable) {
export function register(id: string, callable: Callable) {
if (typeof id !== "string") {
throw Error('Id must be a string');
}
Expand All @@ -44,7 +33,7 @@ export function register(id, callable) {
callables.set(id, callable);
}

appliers["CALL"] = async function(state) {
appliers["CALL"] = async function(state: State) {
const id = this["id"];
const args = this["args"];

Expand All @@ -61,12 +50,12 @@ appliers["CALL"] = async function(state) {
const callable = callables.get(id);

try {
await callable(state, args);
await callable!(state, args);
} catch (e) {
if (e !== state.debugState) {
// So they know what happened
console.error(e);
state.debugState.throwError('ValueError', `Callable ${i} did not properly throw an error.`);
state.debugState.throwError('ValueError', `Callable ${id} did not properly throw an error.`);
}
// They properly threw the error
throw e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,24 @@
*/

import {photocopy, photomerge} from "./patchsteps-utils.js";
import {AnyPatchStep, BasePatchStep, DiffSettings, Index, PatchFile, PatchStep, unsafeAssert} from './types.js'

/**
* A difference heuristic.
* @param {any} a The first value to check.
* @param {any} b The second value to check.
* @param {any} settings The involved control settings.
* @returns {number} A difference value from 0 (same) to 1 (different).
* @param a The first value to check.
* @param b The second value to check.
* @param settings The involved control settings.
* @returns A difference value from 0 (same) to 1 (different).
*/
function diffHeuristic(a, b, settings) {
if ((a === null) && (b === null))
function diffHeuristic(a: unknown, b: unknown, settings: DiffSettings): number {
if ((a === null) && (b === null) || (a === undefined) && (b === undefined))
return 0;
if ((a === null) || (b === null))
return null;
if ((a === null) || (b === null) || (a === undefined) || (b === undefined))
return 1;
if (a.constructor !== b.constructor)
return 1;

if (a.constructor === Array) {
if (Array.isArray(a) && Array.isArray(b)) {
let array = diffArrayHeuristic(a, b, settings);
if (array.length == 0)
return 0;
Expand All @@ -50,8 +51,10 @@ function diffHeuristic(a, b, settings) {
}
}
return changes / array.length;
} else if (a.constructor === Object) {
let total = [];
} else if (a.constructor === Object && b.constructor === Object) {
unsafeAssert<Record<string, unknown>>(a);
unsafeAssert<Record<string, unknown>>(b);
let total: string[] = [];
for (let k in a)
total.push(k);
for (let k in b)
Expand All @@ -64,7 +67,7 @@ function diffHeuristic(a, b, settings) {
} else if ((total[i] in b) && !(total[i] in a)) {
change += settings.diffAddDelKey;
} else {
change += diffHeuristic(a[total[i]], b[total[i]], settings) * settings.diffMulSameKey;
change += diffHeuristic((a as Record<string, unknown>)[total[i]], (b as Record<string, unknown>)[total[i]], settings) * settings.diffMulSameKey;
}
}
if (total.length != 0)
Expand All @@ -91,13 +94,13 @@ function diffHeuristic(a, b, settings) {
* The actual implementation is different to this description, but follows the same rules.
* Stack A and the output are the same.
*/
function diffArrayHeuristic(a, b, settings) {
function diffArrayHeuristic(a: unknown[], b: unknown[], settings: DiffSettings) {
const lookahead = settings.arrayLookahead;
let sublog = [];
let sublog: string[] = [];
let ia = 0;
for (let i = 0; i < b.length; i++) {
let validDif = 2;
let validSrc = null;
let validSrc: number | null = null;
for (let j = ia; j < Math.min(ia + lookahead, a.length); j++) {
let dif = diffHeuristic(a[j], b[i], settings);
if (dif < validDif) {
Expand Down Expand Up @@ -132,24 +135,25 @@ function diffArrayHeuristic(a, b, settings) {

/**
* Diffs two objects. This is actually an outer wrapper, which provides default settings along with optimization.
*
* @param {any} a The original value
* @param {any} b The target value
* @param {object} [settings] Optional bunch of settings. May include "comment".
* @return {object[]|null} Null if unpatchable (this'll never occur for two Objects or two Arrays), Array of JSON-ready Patch Steps otherwise
*
* @param a The original value
* @param b The target value
* @param settings Optional bunch of settings. May include "comment".
* @return Null if unpatchable (this'll never occur for two Objects or two Arrays), Array of JSON-ready Patch Steps otherwise
*/
export function diff(a, b, settings) {
export function diff(a: unknown, b: unknown, settings: Partial<DiffSettings>) {
let trueSettings = photocopy(defaultSettings);
if (settings !== void 0)
photomerge(trueSettings, settings);
if (trueSettings.comment !== void 0)
trueSettings.commentValue = trueSettings.comment;

let result = trueSettings.diffCore(a, b, trueSettings);
if (!result) return null;
if (trueSettings.optimize) {
for (let i = 1; i < result.length; i++) {
let here = result[i];
let prev = result[i - 1];
let prev: AnyPatchStep = result[i - 1];
let optimizedOut = false;
if (here["type"] == "EXIT") {
if (prev["type"] == "EXIT") {
Expand All @@ -158,7 +162,8 @@ export function diff(a, b, settings) {
here["count"] = 1;
if (!("count" in prev))
prev["count"] = 1;
prev["count"] += here["count"];
unsafeAssert<PatchStep.EXIT & {count: number}>(prev);
prev["count"] += here["count"]!;
// Copy comments backwards to try and preserve the unoptimized autocommenter semantics
if ("comment" in here)
prev["comment"] = here["comment"];
Expand All @@ -167,9 +172,9 @@ export function diff(a, b, settings) {
} else if (here["type"] == "ENTER") {
if (prev["type"] == "ENTER") {
// Crush ENTERs
if (prev["index"].constructor !== Array)
if (!Array.isArray(prev["index"]))
prev["index"] = [prev["index"]];
if (here["index"].constructor !== Array)
if (!Array.isArray(here["index"]))
here["index"] = [here["index"]];
prev["index"] = prev["index"].concat(here["index"]);
optimizedOut = true;
Expand All @@ -186,24 +191,24 @@ export function diff(a, b, settings) {

/**
* Adds a comment to the step if there is a comment in settings.commentValue.
* @param {object} step The step to add to.
* @param {object} settings The settings.
* @param step The step to add to.
* @param settings The settings.
*/
export function diffApplyComment(step, settings) {
export function diffApplyComment<T extends BasePatchStep>(step: T, settings: DiffSettings) {
if (settings.commentValue !== void 0)
step.comment = settings.commentValue;
return step;
}

/**
* Handles the bookkeeping in settings necessary when entering a level of the diff.
* @param {any} a The original value
* @param {any} b The target value
* @param {string | number} index The index.
* @param {object} settings Settings.
* @return {object[]|null} See diff for more details
* @param a The original value
* @param b The target value
* @param index The index.
* @param settings Settings.
* @return See diff for more details
*/
export function diffEnterLevel(a, b, index, settings) {
export function diffEnterLevel(a: unknown, b: unknown, index: Index, settings: DiffSettings): AnyPatchStep[] | null {
settings.path.push(index);
if (settings.comment !== void 0)
settings.commentValue = settings.comment + "." + settings.path.join(".");
Expand All @@ -213,16 +218,16 @@ export function diffEnterLevel(a, b, index, settings) {
}

// This is the default diffCore.
function diffInterior(a, b, settings) {
if ((a === null) && (b === null))
function diffInterior(a: unknown, b: unknown, settings: DiffSettings) {
if ((a === null) && (b === null) || (a === undefined) && (b === undefined))
return [];
if ((a === null) || (b === null))
if ((a === null) || (b === null) || (a === undefined) || (b === undefined))
return null;
if (a.constructor !== b.constructor)
return null;
let log = [];
let log: AnyPatchStep[] = [];

if (a.constructor === Array) {
if (Array.isArray(a) && Array.isArray(b)) {
let array = diffArrayHeuristic(a, b, settings);
let ai = 0;
let bi = 0;
Expand All @@ -232,10 +237,10 @@ function diffInterior(a, b, settings) {
// At patch time, a[ai + x] for arbitrary 'x' is in the live array at [bi + x]
for (let i = 0; i < array.length; i++) {
if (array[i] == "POPA") {
log.push(diffApplyComment({"type": "REMOVE_ARRAY_ELEMENT", "index": bi}, settings));
log.push(diffApplyComment({"type": "REMOVE_ARRAY_ELEMENT", "index": bi} as PatchStep.REMOVE_ARRAY_ELEMENT, settings));
ai++;
} else if (array[i] == "INSERT") {
let insertion = diffApplyComment({"type": "ADD_ARRAY_ELEMENT", "index": bi, "content": photocopy(b[bi])}, settings);
let insertion = diffApplyComment({"type": "ADD_ARRAY_ELEMENT", "index": bi, "content": photocopy(b[bi])} as PatchStep.ADD_ARRAY_ELEMENT, settings);
// Is this a set of elements being inserted at the end?
let j;
for (j = i + 1; j < array.length; j++)
Expand All @@ -255,17 +260,22 @@ function diffInterior(a, b, settings) {
log.push({"type": "EXIT"});
}
} else {
log.push(diffApplyComment({"type": "SET_KEY", "index": bi, "content": photocopy(b[bi])}, settings));
log.push(diffApplyComment({"type": "SET_KEY", "index": bi, "content": photocopy(b[bi])} as PatchStep.SET_KEY, settings));
}
ai++;
bi++;
}
}
} else if (a.constructor === Object) {
} else if (a.constructor === Object && b.constructor === Object) {
unsafeAssert<Record<string, unknown>>(a);
unsafeAssert<Record<string, unknown>>(b);
for (let k in a) {
if (k in b) {
unsafeAssert<Record<string, unknown>>(a);
unsafeAssert<Record<string, unknown>>(b);

if (diffHeuristic(a[k], b[k], settings) >= settings.trulyDifferentThreshold) {
log.push(diffApplyComment({"type": "SET_KEY", "index": k, "content": photocopy(b[k])}, settings));
log.push(diffApplyComment({"type": "SET_KEY", "index": k, "content": photocopy(b[k])} as PatchStep.SET_KEY, settings));
} else {
let xd = diffEnterLevel(a[k], b[k], k, settings);
if (xd != null) {
Expand All @@ -276,16 +286,16 @@ function diffInterior(a, b, settings) {
}
} else {
// should it happen? probably not. will it happen? maybe
log.push(diffApplyComment({"type": "SET_KEY", "index": k, "content": photocopy(b[k])}, settings));
log.push(diffApplyComment({"type": "SET_KEY", "index": k, "content": photocopy(b[k])} as PatchStep.SET_KEY, settings));
}
}
} else {
log.push(diffApplyComment({"type": "SET_KEY", "index": k}, settings));
log.push(diffApplyComment({"type": "SET_KEY", "index": k} as PatchStep.SET_KEY, settings));
}
}
for (let k in b)
if (!(k in a))
log.push(diffApplyComment({"type": "SET_KEY", "index": k, "content": photocopy(b[k])}, settings));
log.push(diffApplyComment({"type": "SET_KEY", "index": k, "content": photocopy((b as Record<string, unknown>)[k])} as PatchStep.SET_KEY, settings));
} else if (a != b) {
return null;
}
Expand Down
Loading