Last active
February 17, 2020 23:35
-
-
Save airportyh/d035d9b51e305e30886f2f5f2c2c04ca to your computer and use it in GitHub Desktop.
Test case for suspected reduce bug.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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