Skip to content

Instantly share code, notes, and snippets.

@airportyh
Last active February 17, 2020 23:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save airportyh/d035d9b51e305e30886f2f5f2c2c04ca to your computer and use it in GitHub Desktop.
Save airportyh/d035d9b51e305e30886f2f5f2c2c04ca to your computer and use it in GitHub Desktop.
Test case for suspected reduce bug.
// Runtime functions
const $history = [];
let $historyCursor = -1;
let $stack = [];
let $nextHeapId = 1;
let $heap = {};
let $body = $nativeDomToVDom(document.body);
let $heapOfLastDomSync = $heap;
function $pushFrame(funName, variables) {
const newFrame = { funName, parameters: variables, variables };
$stack = [...$stack, newFrame];
}
function $setVariable(varName, value, line) {
const frame = $stack[$stack.length - 1];
const newFrame = {
...frame,
variables: { ...frame.variables, [varName]: value }
};
$stack = [...$stack.slice(0, $stack.length - 1), newFrame];
}
function $heapAllocate(value) {
const id = $nextHeapId;
$nextHeapId++;
$heap = {
...$heap,
[id]: value
};
return id;
}
function $getVariable(varName) {
return $stack[$stack.length - 1].variables[varName];
}
function $heapAccess(id) {
if (typeof id === "string") {
return id;
}
return $heap[id];
}
function $nativeDomToVDom(node) {
if (node.nodeType === Node.ELEMENT_NODE) {
const tag = node.tagName.toLowerCase();
const element = { tag };
const elementId = $heapAllocate(element);
const attributeNames = node.getAttributeNames();
if (attributeNames.length > 0) {
const attrs = {};
for (let i = 0; i < attributeNames.length; i++) {
const attrName = attributeNames[i];
attrs[attrName] = node.getAttribute(attrName);
}
element.attrs = $heapAllocate(attrs);
}
const childNodes = node.childNodes;
if (childNodes.length > 0) {
const childNodeResults = [];
for (let i = 0; i < childNodes.length; i++) {
childNodeResults[i] = $nativeDomToVDom(childNodes[i]);
}
element.children = $heapAllocate(childNodeResults);
}
return elementId;
} else if (node.nodeType === Node.TEXT_NODE) {
return node.data;
} else {
throw new Error("Unsupported node type: " + node.nodeType);
}
}
function createElement(tag, attrs, children) {
const element = { tag };
if (attrs) {
element.attrs = attrs;
}
if (children) {
element.children = children;
}
return $heapAllocate(element);
}
function getDocumentBody() {
return $body;
}
function appendTo(parentId, childId) {
const parent = $heapAccess(parentId);
const children = $heapAccess(parent.children) || [];
const newChildren = $heapAllocate([...children, childId]);
const newParent = {
...parent,
children: newChildren
};
$heap = {
...$heap,
[parentId]: newParent
};
syncVDomToDom()
}
function setText(elementId, text) {
const element = $heapAccess(elementId);
const newChildren = $heapAllocate([text]);
const newElement = {
...element,
children: newChildren
};
$heap = {
...$heap,
[elementId]: newElement
};
syncVDomToDom()
}
function setStyle(elementId, stylesId) {
const styles = $heapAccess(stylesId);
const element = $heapAccess(elementId);
const attrs = $heapAccess(element.attrs);
const oldStyles = $heapAccess(attrs.style);
const newAttrs = $heapAllocate({
...attrs,
style: {
...oldStyles,
...styles
}
});
const newElement = {
...element,
attrs: newAttrs
};
$heap = {
...$heap,
[elementId]: newElement
};
syncVDomToDom()
}
// Synchronises the current virtual DOM state contained in `$body` to `document.body`.
// This works by calculating the difference of `$body` between its state since the
// last time it was synchronised and its current state, this is done with the `compare`
// function. Then, for each difference, we mutate the native DOM with that difference.
function syncVDomToDom() {
const diff = compare(1, $heapOfLastDomSync, 1, $heap);
$heapOfLastDomSync = $heap;
}
// Deep compare of two objects within two different heaps. This is for
// the virtual DOM difference calculation.
function compare(source, heap1, destination, heap2) {
return compareAt([], source, destination);
function isObject(value) {
const type = typeof value
return value != null && (type === 'object' || type === 'function')
}
function difference(arr1, arr2) {
const result = [];
for (let i = 0; i < arr1.length; i++) {
if (arr2.indexOf(arr1[i]) === -1) {
result.push(arr1[i]);
}
}
return result;
}
function intersection(arr1, arr2) {
const result = [];
for (let i = 0; i < arr1.length; i++) {
if (arr2.indexOf(arr1[i]) !== -1) {
result.push(arr1[i]);
}
}
return result;
}
function heapAccess(id, heap) {
if (typeof id === "string") {
return id;
}
return heap[id];
}
function compareAt(path, source, destination) {
if (isObject(heapAccess(source, heap1)) && isObject(heapAccess(destination, heap2))) {
return compareObjectsAt(path, source, destination);
} else {
if (source === destination) {
return [];
} else {
return [
{
type: "replacement",
path: path,
oldValue: source,
value: destination
}
];
}
}
}
function compareObjectsAt(path, source, destination) {
source = heapAccess(source, heap1);
destination = heapAccess(destination, heap2);
const sourceKeys = Object.keys(source);
const destinationKeys = Object.keys(destination);
const sourceOnlyKeys = difference(sourceKeys, destinationKeys);
const commonKeys = intersection(sourceKeys, destinationKeys);
const destinationOnlyKeys = difference(destinationKeys, sourceKeys);
const additions = destinationOnlyKeys.map((key) => ({
type: "addition",
path: [...path, key],
value: destination[key]
}));
const removals = sourceOnlyKeys.map((key) => ({
type: "deletion",
path: [...path, key]
}));
let iterated = [];
const childDiffs = commonKeys
.reduce((diffs, key, idx) => {
iterated.push([key, idx]);
const result = compareAt([...path, key], source[key], destination[key]);
return diffs.concat(result);
}, []);
if (iterated.length !== commonKeys.length) {
throw new Error("iterated.length is not equal to commonKeys.length. commonKeys: " + JSON.stringify(commonKeys) + ", iterated: " + JSON.stringify(iterated));
}
return [
...additions,
...removals,
...childDiffs
];
}
}
async function main() {
var $immediateReturnValue;
$pushFrame("main", { });
$setVariable("body", getDocumentBody(), 2);
$setVariable("table", createElement("table", $heapAllocate({ border: "1" })), 3);
appendTo($getVariable("body"), $getVariable("table"));
$setVariable("i", 1, 5);
while (($getVariable("i") <= 10)) {
$setVariable("row", createElement("tr"), 7);
appendTo($getVariable("table"), $getVariable("row"));
$setVariable("j", 1, 9);
while (($getVariable("j") <= 10)) {
$setVariable("product", (($getVariable("i") * $getVariable("j")) + ""), 11);
$setVariable("cell", createElement("td"), 12);
appendTo($getVariable("row"), $getVariable("cell"));
setText($getVariable("cell"), $getVariable("product"));
$setVariable("j", ($getVariable("j") + 1), 15);
}
$setVariable("i", ($getVariable("i") + 1), 17);
}
}
main();
<!doctype html>
<html lang="en">
<head>
<title>Test Bug</title>
<meta charset="utf-8"/>
</head>
<body>
<script src="test-bug.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment