Commit a97a362d authored by Sigurd Schneider's avatar Sigurd Schneider Committed by Commit Bot

[turbolizer] Add support for inlined functions

This CL adds support for inlined functions in Turbolizer. It is also a
refactoring of the Turbolizer code-base. Most importantly, handling of
source positions changed to exact source positions, and not code ranges.
This improves selection interoperability between different phase views.

A separate CL changes the Turbolizer JSON format to include inlining
information. This Turbolizer update, however, is intended to be backwards
compatible with the JSON format Turbolizer generated before the JSON
format change.


Bug: v8:7327
Change-Id: Ic67506a6f3a36fe98c012b1e76994972779c1fd2
Reviewed-on: https://chromium-review.googlesource.com/1032784
Commit-Queue: Sigurd Schneider <sigurds@chromium.org>
Reviewed-by: 's avatarDaniel Clifford <danno@chromium.org>
Cr-Commit-Position: refs/heads/master@{#53213}
parent 4b6263be
......@@ -5,166 +5,257 @@
"use strict";
class CodeView extends View {
constructor(divID, PR, sourceText, sourcePosition, broker) {
super(divID, broker, null, false);
static get MAIN_SOURCE() {
return "main function";
}
static get INLINED_SOURCE() {
return "inlined function";
}
createViewElement() {
const sourceContainer = document.createElement("div");
sourceContainer.classList.add("source-container");
return sourceContainer;
}
constructor(parentId, broker, sourceResolver, sourceFunction, codeMode) {
super(parentId, broker, null, false);
let view = this;
view.PR = PR;
view.mouseDown = false;
view.broker = broker;
view.allSpans = [];
var selectionHandler = {
clear: function() { broker.clear(selectionHandler); },
select: function(items, selected) {
var handler = this;
var broker = view.broker;
for (let span of items) {
if (selected) {
span.classList.add("selected");
} else {
span.classList.remove("selected");
}
}
var locations = [];
for (var span of items) {
locations.push({pos_start: span.start, pos_end: span.end});
}
broker.clear(selectionHandler);
broker.select(selectionHandler, locations, selected);
view.source = null;
view.sourceResolver = sourceResolver;
view.source = sourceFunction;
view.codeMode = codeMode;
this.lineToSourcePositions = new Map();
this.sourcePositionToHtmlElement = new Map();
this.showAdditionalInliningPosition = false;
const selectionHandler = {
clear: function () {
view.selection.clear();
view.updateSelection();
broker.broadcastClear(this)
},
selectionDifference: function(span1, inclusive1, span2, inclusive2) {
var pos1 = span1.start;
var pos2 = span2.start;
var result = [];
var lineListDiv = view.divNode.firstChild.firstChild.childNodes;
for (var i = 0; i < lineListDiv.length; i++) {
var currentLineElement = lineListDiv[i];
var spans = currentLineElement.childNodes;
for (var j = 0; j < spans.length; ++j) {
var currentSpan = spans[j];
if (currentSpan.start > pos1 ||
(inclusive1 && currentSpan.start == pos1)) {
if (currentSpan.start < pos2 ||
(inclusive2 && currentSpan.start == pos2)) {
result.push(currentSpan);
}
}
}
select: function (sourcePositions, selected) {
const locations = [];
for (var sourcePosition of sourcePositions) {
locations.push(sourcePosition);
sourceResolver.addInliningPositions(sourcePosition, locations);
}
return result;
if (locations.length == 0) return;
view.selection.select(locations, selected);
view.updateSelection();
broker.broadcastSourcePositionSelect(this, locations, selected);
},
brokeredSelect: function(locations, selected) {
let firstSelect = view.selection.isEmpty();
for (let location of locations) {
let start = location.pos_start;
let end = location.pos_end;
if (start && end) {
let lower = 0;
let upper = view.allSpans.length;
if (upper > 0) {
while ((upper - lower) > 1) {
var middle = Math.floor((upper + lower) / 2);
var lineStart = view.allSpans[middle].start;
if (lineStart < start) {
lower = middle;
} else if (lineStart > start) {
upper = middle;
} else {
lower = middle;
break;
}
}
var currentSpan = view.allSpans[lower];
var currentLineElement = currentSpan.parentNode;
if ((currentSpan.start <= start && start < currentSpan.end) ||
(currentSpan.start <= end && end < currentSpan.end)) {
if (firstSelect) {
makeContainerPosVisible(
view.divNode, currentLineElement.offsetTop);
firstSelect = false;
}
view.selection.select(currentSpan, selected);
}
}
}
brokeredSourcePositionSelect: function (locations, selected) {
const firstSelect = view.selection.isEmpty();
for (const location of locations) {
const translated = sourceResolver.translateToSourceId(view.source.sourceId, location);
if (!translated) continue;
view.selection.select(translated, selected);
}
view.updateSelection(firstSelect);
},
brokeredClear: function () {
view.selection.clear();
view.updateSelection();
},
brokeredClear: function() { view.selection.clear(); },
};
view.selection = new Selection(selectionHandler);
broker.addSelectionHandler(selectionHandler);
view.selection = new Selection(sourcePositionToStringKey);
broker.addSourcePositionHandler(selectionHandler);
this.selectionHandler = selectionHandler;
this.initializeCode();
}
view.handleSpanMouseDown = function(e) {
e.stopPropagation();
if (!e.shiftKey) {
view.selection.clear();
}
view.selection.select(this, true);
view.mouseDown = true;
addSourcePositionToLine(lineNumber, sourcePosition) {
const lineNumberString = anyToString(lineNumber);
if (!this.lineToSourcePositions.has(lineNumberString)) {
this.lineToSourcePositions.set(lineNumberString, []);
}
this.lineToSourcePositions.get(lineNumberString).push(sourcePosition);
}
view.handleSpanMouseMove = function(e) {
if (view.mouseDown) {
view.selection.extendTo(this);
}
addHtmlElementToSourcePosition(sourcePosition, element) {
const key = sourcePositionToStringKey(sourcePosition);
if (this.sourcePositionToHtmlElement.has(key)) {
console.log("Warning: duplicate source position", sourcePosition);
}
this.sourcePositionToHtmlElement.set(key, element);
}
view.handleCodeMouseDown = function(e) { view.selection.clear(); }
getHtmlElementForSourcePosition(sourcePosition) {
const key = sourcePositionToStringKey(lineNumber);
return this.sourcePositionToHtmlElement.get(key);
}
updateSelection(scrollIntoView) {
const mkVisible = new ViewElements(this.divNode.parentNode);
for (const [sp, el] of this.sourcePositionToHtmlElement.entries()) {
const isSelected = this.selection.isKeySelected(sp);
mkVisible.consider(el, isSelected);
el.classList.toggle("selected", isSelected);
}
mkVisible.apply(scrollIntoView);
}
document.addEventListener('mouseup', function(e) {
view.mouseDown = false;
}, false);
initializeContent(data, rememberedSelection) {
this.data = data;
}
view.initializeCode(sourceText, sourcePosition);
getCodeHtmlElementName() {
return `source-pre-${this.source.sourceId}`;
}
initializeContent(data, rememberedSelection) { this.data = data; }
getCodeHeaderHtmlElementName() {
return `source-pre-${this.source.sourceId}-header`;
}
initializeCode(sourceText, sourcePosition) {
getHtmlCodeLines() {
const lineListDiv = this.divNode.querySelector(`#${this.getCodeHtmlElementName()} ol`).childNodes;
return lineListDiv;
}
onSelectLine(lineNumber, doClear) {
const sourcePositions = this.lineToSourcePositions.get(anyToString(lineNumber));
if (doClear) {
this.selectionHandler.clear();
}
if (!sourcePositions) return;
this.selectionHandler.select(sourcePositions, undefined);
}
onSelectSourcePosition(sourcePosition, doClear) {
if (doClear) {
this.selectionHandler.clear();
}
this.selectionHandler.select([sourcePosition], undefined);
}
initializeCode() {
var view = this;
const source = this.source;
const sourceText = source.sourceText;
if (!sourceText) return;
const sourceContainer = view.divNode;
if (this.codeMode == CodeView.MAIN_SOURCE) {
sourceContainer.classList.add("main-source");
} else {
sourceContainer.classList.add("inlined-source");
}
var codeHeader = document.createElement("div");
codeHeader.setAttribute("id", this.getCodeHeaderHtmlElementName());
codeHeader.classList.add("code-header");
var codeFileFunction = document.createElement("div");
codeFileFunction.classList.add("code-file-function");
codeFileFunction.innerHTML = `${source.sourceName}:${source.functionName}`;
codeHeader.appendChild(codeFileFunction);
var codeModeDiv = document.createElement("div");
codeModeDiv.classList.add("code-mode");
codeModeDiv.innerHTML = `${this.codeMode}`;
codeHeader.appendChild(codeModeDiv);
var clearDiv = document.createElement("div");
clearDiv.style = "clear:both;"
codeHeader.appendChild(clearDiv);
sourceContainer.appendChild(codeHeader);
var codePre = document.createElement("pre");
codePre.setAttribute("id", this.getCodeHtmlElementName());
codePre.classList.add("prettyprint");
view.divNode.innerHTML = "";
view.divNode.appendChild(codePre);
sourceContainer.appendChild(codePre);
codeHeader.onclick = function myFunction() {
if (codePre.style.display === "none") {
codePre.style.display = "block";
} else {
codePre.style.display = "none";
}
}
if (sourceText != "") {
codePre.classList.add("linenums");
codePre.textContent = sourceText;
try {
// Wrap in try to work when offline.
view.PR.prettyPrint();
PR.prettyPrint(undefined, sourceContainer);
} catch (e) {
console.log(e);
}
view.divNode.onmousedown = this.handleCodeMouseDown;
view.divNode.onclick = function (e) {
view.selectionHandler.clear();
}
var base = sourcePosition;
var current = 0;
var lineListDiv = view.divNode.firstChild.firstChild.childNodes;
const base = source.startPosition;
let current = 0;
const lineListDiv = this.getHtmlCodeLines();
let newlineAdjust = 0;
for (let i = 0; i < lineListDiv.length; i++) {
var currentLineElement = lineListDiv[i];
// Line numbers are not zero-based.
const lineNumber = i + 1;
const currentLineElement = lineListDiv[i];
currentLineElement.id = "li" + i;
var pos = base + current;
currentLineElement.pos = pos;
var spans = currentLineElement.childNodes;
currentLineElement.dataset.lineNumber = lineNumber;
const spans = currentLineElement.childNodes;
for (let j = 0; j < spans.length; ++j) {
var currentSpan = spans[j];
if (currentSpan.nodeType == 1) {
currentSpan.start = pos;
currentSpan.end = pos + currentSpan.textContent.length;
currentSpan.onmousedown = this.handleSpanMouseDown;
currentSpan.onmousemove = this.handleSpanMouseMove;
view.allSpans.push(currentSpan);
}
const currentSpan = spans[j];
const pos = base + current;
const end = pos + currentSpan.textContent.length;
current += currentSpan.textContent.length;
pos = base + current;
this.insertSourcePositions(currentSpan, lineNumber, pos, end, newlineAdjust);
newlineAdjust = 0;
}
this.insertLineNumber(currentLineElement, lineNumber);
while ((current < sourceText.length) &&
(sourceText[current] == '\n' || sourceText[current] == '\r')) {
(sourceText[current] == '\n' || sourceText[current] == '\r')) {
++current;
++newlineAdjust;
}
}
}
}
deleteContent() {}
insertSourcePositions(currentSpan, lineNumber, pos, end, adjust) {
const view = this;
const sps = this.sourceResolver.sourcePositionsInRange(this.source.sourceId, pos - adjust, end);
for (const sourcePosition of sps) {
view.addSourcePositionToLine(lineNumber, sourcePosition);
const textnode = currentSpan.tagName == 'SPAN' ? currentSpan.firstChild : currentSpan;
const replacementNode = textnode.splitText(Math.max(0, sourcePosition.scriptOffset - pos));
const span = document.createElement('span');
span.setAttribute("scriptOffset", sourcePosition.scriptOffset);
span.classList.add("source-position")
const marker = document.createElement('span');
marker.classList.add("marker")
span.appendChild(marker);
const inlining = this.sourceResolver.getInliningForPosition(sourcePosition);
if (inlining != undefined && view.showAdditionalInliningPosition) {
const sourceName = this.sourceResolver.getSourceName(inlining.sourceId);
const inliningMarker = document.createElement('span');
inliningMarker.classList.add("inlining-marker")
inliningMarker.setAttribute("data-descr", `${sourceName} was inlined here`)
span.appendChild(inliningMarker);
}
span.onclick = function (e) {
e.stopPropagation();
view.onSelectSourcePosition(sourcePosition, !e.shiftKey)
};
view.addHtmlElementToSourcePosition(sourcePosition, span);
textnode.parentNode.insertBefore(span, replacementNode);
}
}
insertLineNumber(lineElement, lineNumber) {
const view = this;
const lineNumberElement = document.createElement("div");
lineNumberElement.classList.add("line-number");
lineNumberElement.innerText = lineNumber;
lineNumberElement.onclick = function (e) {
e.stopPropagation();
view.onSelectLine(lineNumber, !e.shiftKey);
}
lineElement.insertBefore(lineNumberElement, lineElement.firstChild)
}
deleteContent() { }
}
......@@ -11,7 +11,6 @@ var SOURCE_PANE_ID = 'left';
var SOURCE_COLLAPSE_ID = 'source-shrink';
var SOURCE_EXPAND_ID = 'source-expand';
var INTERMEDIATE_PANE_ID = 'middle';
var EMPTY_PANE_ID = 'empty';
var GRAPH_PANE_ID = 'graph';
var SCHEDULE_PANE_ID = 'schedule';
var GENERATED_PANE_ID = 'right';
......
......@@ -5,22 +5,39 @@
"use strict";
class DisassemblyView extends TextView {
constructor(id, broker) {
super(id, broker, null, false);
createViewElement() {
const pane = document.createElement('div');
pane.setAttribute('id', "disassembly");
pane.innerHTML =
`<pre id='disassembly-text-pre' class='prettyprint prettyprinted'>
<ul id='disassembly-list' class='nolinenums noindent'>
</ul>
</pre>`;
return pane;
}
constructor(parentId, broker) {
super(parentId, broker, null, false);
let view = this;
let ADDRESS_STYLE = {
css: 'tag',
location: function(text) {
ADDRESS_STYLE.last_address = text;
return undefined;
assignSourcePosition: function (text) {
return SOURCE_POSITION_HEADER_STYLE.currentSourcePosition;
},
linkHandler: function (text, fragment) {
if (fragment.sourcePosition === undefined) return undefined;
return (e) => {
e.stopPropagation();
if (!e.shiftKey) {
view.sourcePositionSelectionHandler.clear();
}
view.sourcePositionSelectionHandler.select([fragment.sourcePosition], true);
};
}
};
let ADDRESS_LINK_STYLE = {
css: 'tag',
link: function(text) {
view.select(function(location) { return location.address == text; }, true, true);
}
css: 'tag'
};
let UNCLASSIFIED_STYLE = {
css: 'com'
......@@ -33,52 +50,47 @@ class DisassemblyView extends TextView {
};
let POSITION_STYLE = {
css: 'com',
location: function(text) {
location: function (text) {
view.pos_start = Number(text);
}
};
let OPCODE_STYLE = {
css: 'kwd',
location: function(text) {
if (BLOCK_HEADER_STYLE.block_id != undefined) {
return {
address: ADDRESS_STYLE.last_address,
block_id: BLOCK_HEADER_STYLE.block_id
};
} else {
return {
address: ADDRESS_STYLE.last_address
};
}
}
};
const BLOCK_HEADER_STYLE = {
css: 'com',
block_id: -1,
location: function(text) {
css: ['com', 'block'],
blockId: function (text) {
let matches = /\d+/.exec(text);
if (!matches) return undefined;
BLOCK_HEADER_STYLE.block_id = Number(matches[0]);
return {
block_id: BLOCK_HEADER_STYLE.block_id
};
return BLOCK_HEADER_STYLE.block_id;
},
linkHandler: function (text) {
let matches = /\d+/.exec(text);
if (!matches) return undefined;
const blockId = matches[0];
return function (e) {
e.stopPropagation();
if (!e.shiftKey) {
view.selectionHandler.clear();
}
view.blockSelectionHandler.select([blockId], true);
};
}
};
const SOURCE_POSITION_HEADER_STYLE = {
css: 'com',
location: function(text) {
let matches = /(\d+):(\d+)/.exec(text);
sourcePosition: function (text) {
let matches = view.SOURCE_POSITION_HEADER_REGEX.exec(text);
if (!matches) return undefined;
let li = Number(matches[1]);
if (view.pos_lines === null) return undefined;
let pos = view.pos_lines[li-1] + Number(matches[2]);
return {
pos_start: pos,
pos_end: pos + 1
};
const scriptOffset = Number(matches[3]);
const inliningId = matches[1] === 'not inlined' ? -1 : Number(matches[2]);
const sp = { scriptOffset: scriptOffset, inliningId: inliningId };
SOURCE_POSITION_HEADER_STYLE.currentSourcePosition = sp;
return sp;
},
};
view.SOURCE_POSITION_HEADER_REGEX = /^(\s*-- .+:)(\d+:\d+)( --)/;
view.SOURCE_POSITION_HEADER_REGEX = /^\s*--[^<]*<.*(not inlined|inlined\((\d+)\)):(\d+)>\s*--/;
let patterns = [
[
[/^0x[0-9a-f]{8,16}/, ADDRESS_STYLE, 1],
......@@ -119,36 +131,6 @@ class DisassemblyView extends TextView {
view.setPatterns(patterns);
}
lineLocation(li) {
let view = this;
let result = undefined;
for (let i = 0; i < li.children.length; ++i) {
let fragment = li.children[i];
let location = fragment.location;
if (location != null) {
if (location.block_id != undefined) {
if (result === undefined) result = {};
result.block_id = location.block_id;
}
if (location.address != undefined) {
if (result === undefined) result = {};
result.address = location.address;
}
if (location.pos_start != undefined && location.pos_end != undefined) {
if (result === undefined) result = {};
result.pos_start = location.pos_start;
result.pos_end = location.pos_end;
}
else if (view.pos_start != -1) {
if (result === undefined) result = {};
result.pos_start = view.pos_start;
result.pos_end = result.pos_start + 1;
}
}
}
return result;
}
initializeContent(data, rememberedSelection) {
this.data = data;
super.initializeContent(data, rememberedSelection);
......@@ -164,13 +146,13 @@ class DisassemblyView extends TextView {
// Comment lines for line 0 include sourcePosition already, only need to
// add sourcePosition for lines > 0.
view.pos_lines[0] = sourcePosition;
if (sourceText != "") {
if (sourceText && sourceText != "") {
let base = sourcePosition;
let current = 0;
let source_lines = sourceText.split("\n");
for (let i = 1; i < source_lines.length; i++) {
// Add 1 for newline character that is split off.
current += source_lines[i-1].length + 1;
current += source_lines[i - 1].length + 1;
view.pos_lines[i] = base + current;
}
}
......@@ -209,16 +191,6 @@ class DisassemblyView extends TextView {
processLine(line) {
let view = this;
let func = function(match, p1, p2, p3) {
let nums = p2.split(":");
let li = Number(nums[0]);
let pos = Number(nums[1]);
if(li === 0)
pos -= view.pos_lines[0];
li++;
return p1 + li + ":" + pos + p3;
};
line = line.replace(view.SOURCE_POSITION_HEADER_REGEX, func);
let fragments = super.processLine(line);
// Add profiling data per instruction if available.
......@@ -230,7 +202,7 @@ class DisassemblyView extends TextView {
let count = view.addr_event_counts[event][matches[1]];
let str = " ";
let css_cls = "prof";
if(count !== undefined) {
if (count !== undefined) {
let perc = count / view.total_event_counts[event] * 100;
let col = { r: 255, g: 255, b: 255 };
......
// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
"use strict";
class EmptyView extends View {
constructor(id, broker) {
super(id, broker);
this.svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%");
}
initializeContent(data, rememberedSelection) {
this.svg.attr("height", document.documentElement.clientHeight + "px");
}
deleteContent() {
}
}
......@@ -447,7 +447,7 @@ function layoutNodeGraph(graph) {
});
graph.maxBackEdgeNumber = 0;
graph.visibleEdges.each(function (e) {
graph.visibleEdges.selectAll("path").each(function (e) {
if (e.isBackEdge()) {
e.backEdgeNumber = ++graph.maxBackEdgeNumber;
} else {
......
......@@ -4,16 +4,30 @@
"use strict";
function nodeToStringKey(n) {
return "" + n.id;
}
class GraphView extends View {
constructor (d3, id, nodes, edges, broker) {
createViewElement() {
const pane = document.createElement('div');
pane.setAttribute('id', "graph");
return pane;
}
constructor(d3, id, broker) {
super(id, broker);
var graph = this;
var svg = this.divElement.append("svg").attr('version','1.1').attr("width", "100%");
var svg = this.divElement.append("svg").attr('version', '1.1')
.attr("width", "100%")
.attr("height", "100%");
svg.on("mousedown", function (d) { graph.svgMouseDown.call(graph, d); });
svg.on("mouseup", function (d) { graph.svgMouseUp.call(graph, d); });
graph.svg = svg;
graph.nodes = nodes || [];
graph.edges = edges || [];
graph.nodes = [];
graph.edges = [];
graph.minGraphX = 0;
graph.maxGraphX = 1;
......@@ -26,77 +40,52 @@ class GraphView extends View {
justDragged: false,
justScaleTransGraph: false,
lastKeyDown: -1,
showTypes: false
showTypes: false,
hideDead: false
};
var selectionHandler = {
clear: function() {
broker.clear(selectionHandler);
this.selectionHandler = {
clear: function () {
graph.state.selection.clear();
broker.broadcastClear(this);
graph.updateGraphVisibility();
},
select: function(items, selected) {
var locations = [];
for (var d of items) {
if (selected) {
d.classList.add("selected");
} else {
d.classList.remove("selected");
select: function (nodes, selected) {
let locations = [];
for (const node of nodes) {
if (node.sourcePosition) {
locations.push(node.sourcePosition);
}
var data = d.__data__;
locations.push({ pos_start: data.pos, pos_end: data.pos + 1, node_id: data.id});
}
broker.select(selectionHandler, locations, selected);
},
selectionDifference: function(span1, inclusive1, span2, inclusive2) {
// Should not be called
graph.state.selection.select(nodes, selected);
broker.broadcastSourcePositionSelect(this, locations, selected);
graph.updateGraphVisibility();
},
brokeredSelect: function(locations, selected) {
var test = [].entries().next();
var selection = graph.nodes
.filter(function(n) {
var pos = n.pos;
for (var location of locations) {
var start = location.pos_start;
var end = location.pos_end;
var id = location.node_id;
if (end != undefined) {
if (pos >= start && pos < end) {
return true;
}
} else if (start != undefined) {
if (pos === start) {
return true;
}
} else {
if (n.id === id) {
return true;
}
}
}
return false;
brokeredNodeSelect: function (locations, selected) {
let selection = graph.nodes
.filter(function (n) {
return locations.has(nodeToStringKey(n))
&& (!graph.state.hideDead || n.isLive());
});
var newlySelected = new Set();
selection.forEach(function(n) {
newlySelected.add(n);
if (!n.visible) {
n.visible = true;
}
graph.state.selection.select(selection, selected);
// Update edge visibility based on selection.
graph.nodes.forEach((n) => {
if (graph.state.selection.isSelected(n)) n.visible = true;
});
graph.updateGraphVisibility();
graph.visibleNodes.each(function(n) {
if (newlySelected.has(n)) {
graph.state.selection.select(this, selected);
}
graph.edges.forEach(function (e) {
e.visible = e.visible ||
(graph.state.selection.isSelected(e.source) && graph.state.selection.isSelected(e.target));
});
graph.updateGraphVisibility();
graph.viewSelection();
},
brokeredClear: function() {
brokeredClear: function () {
graph.state.selection.clear();
graph.updateGraphVisibility();
}
};
broker.addSelectionHandler(selectionHandler);
broker.addNodeHandler(this.selectionHandler);
graph.state.selection = new Selection(selectionHandler);
graph.state.selection = new Selection(nodeToStringKey);
var defs = svg.append('svg:defs');
defs.append('svg:marker')
......@@ -110,51 +99,47 @@ class GraphView extends View {
.attr('d', 'M0,-4L8,0L0,4');
this.graphElement = svg.append("g");
graph.visibleEdges = this.graphElement.append("g").selectAll("g");
graph.visibleNodes = this.graphElement.append("g").selectAll("g");
graph.visibleEdges = this.graphElement.append("g");
graph.visibleNodes = this.graphElement.append("g");
graph.drag = d3.behavior.drag()
.origin(function(d){
return {x: d.x, y: d.y};
.origin(function (d) {
return { x: d.x, y: d.y };
})
.on("drag", function(args){
.on("drag", function (args) {
graph.state.justDragged = true;
graph.dragmove.call(graph, args);
})
d3.select("#upload").on("click", partial(this.uploadAction, graph));
d3.select("#layout").on("click", partial(this.layoutAction, graph));
d3.select("#show-all").on("click", partial(this.showAllAction, graph));
d3.select("#hide-dead").on("click", partial(this.hideDeadAction, graph));
d3.select("#toggle-hide-dead").on("click", partial(this.toggleHideDead, graph));
d3.select("#hide-unselected").on("click", partial(this.hideUnselectedAction, graph));
d3.select("#hide-selected").on("click", partial(this.hideSelectedAction, graph));
d3.select("#zoom-selection").on("click", partial(this.zoomSelectionAction, graph));
d3.select("#toggle-types").on("click", partial(this.toggleTypesAction, graph));
d3.select("#search-input").on("keydown", partial(this.searchInputAction, graph));
// listen for key events
d3.select(window).on("keydown", function(e){
d3.select(window).on("keydown", function (e) {
graph.svgKeyDown.call(graph);
})
.on("keyup", function(){
.on("keyup", function () {
graph.svgKeyUp.call(graph);
});
svg.on("mousedown", function(d){graph.svgMouseDown.call(graph, d);});
svg.on("mouseup", function(d){graph.svgMouseUp.call(graph, d);});
graph.dragSvg = d3.behavior.zoom()
.on("zoom", function(){
if (d3.event.sourceEvent.shiftKey){
.on("zoom", function () {
if (d3.event.sourceEvent.shiftKey) {
return false;
} else{
} else {
graph.zoomed.call(graph);
}
return true;
})
.on("zoomstart", function(){
.on("zoomstart", function () {
if (!d3.event.sourceEvent.shiftKey) d3.select('body').style("cursor", "move");
})
.on("zoomend", function(){
.on("zoomend", function () {
d3.select('body').style("cursor", "auto");
});
......@@ -184,16 +169,16 @@ class GraphView extends View {
getEdgeFrontier(nodes, inEdges, edgeFilter) {
let frontier = new Set();
nodes.forEach(function(element) {
var edges = inEdges ? element.__data__.inputs : element.__data__.outputs;
for (const n of nodes) {
var edges = inEdges ? n.inputs : n.outputs;
var edgeNumber = 0;
edges.forEach(function(edge) {
edges.forEach(function (edge) {
if (edgeFilter == undefined || edgeFilter(edge, edgeNumber)) {
frontier.add(edge);
}
++edgeNumber;
});
});
}
return frontier;
}
......@@ -204,13 +189,13 @@ class GraphView extends View {
var edgeFrontier = graph.getEdgeFrontier(nodes, inEdges, edgeFilter);
// Control key toggles edges rather than just turning them on
if (d3.event.ctrlKey) {
edgeFrontier.forEach(function(edge) {
edgeFrontier.forEach(function (edge) {
if (edge.visible) {
newState = false;
}
});
}
edgeFrontier.forEach(function(edge) {
edgeFrontier.forEach(function (edge) {
edge.visible = newState;
if (newState) {
var node = inEdges ? edge.source : edge.target;
......@@ -220,9 +205,7 @@ class GraphView extends View {
});
graph.updateGraphVisibility();
if (newState) {
return graph.visibleNodes.filter(function(n) {
return frontier.has(n);
});
return frontier;
} else {
return undefined;
}
......@@ -243,6 +226,7 @@ class GraphView extends View {
this.viewSelection();
}
this.updateGraphVisibility();
this.fitGraphViewToWindow();
}
deleteContent() {
......@@ -263,15 +247,19 @@ class GraphView extends View {
};
}
createGraph(data, initiallyVisibileIds) {
createGraph(data, rememberedSelection) {
var g = this;
g.nodes = data.nodes;
g.nodeMap = [];
g.nodes.forEach(function(n, i){
g.nodes.forEach(function (n, i) {
n.__proto__ = Node;
n.visible = false;
n.x = 0;
n.y = 0;
if (typeof n.pos === "number") {
// Backwards compatibility.
n.sourcePosition = { scriptOffset: n.pos, inliningId: -1 };
}
n.rank = MAX_RANK_SENTINEL;
n.inputs = [];
n.outputs = [];
......@@ -284,12 +272,12 @@ class GraphView extends View {
n.typebbox = g.measureText(n.getDisplayType());
var innerwidth = Math.max(n.labelbbox.width, n.typebbox.width);
n.width = Math.alignUp(innerwidth + NODE_INPUT_WIDTH * 2,
NODE_INPUT_WIDTH);
NODE_INPUT_WIDTH);
var innerheight = Math.max(n.labelbbox.height, n.typebbox.height);
n.normalheight = innerheight + 20;
});
g.edges = [];
data.edges.forEach(function(e, i){
data.edges.forEach(function (e, i) {
var t = g.nodeMap[e.target];
var s = g.nodeMap[e.source];
var newEdge = new Edge(t, e.index, s, e.type);
......@@ -300,10 +288,10 @@ class GraphView extends View {
s.cfg = true;
}
});
g.nodes.forEach(function(n, i) {
n.visible = isNodeInitiallyVisible(n);
if (initiallyVisibileIds != undefined) {
if (initiallyVisibileIds.has(n.id)) {
g.nodes.forEach(function (n, i) {
n.visible = isNodeInitiallyVisible(n) && (!g.state.hideDead || n.isLive());
if (rememberedSelection != undefined) {
if (rememberedSelection.has(nodeToStringKey(n))) {
n.visible = true;
}
}
......@@ -317,25 +305,24 @@ class GraphView extends View {
connectVisibleSelectedNodes() {
var graph = this;
graph.state.selection.selection.forEach(function(element) {
var edgeNumber = 0;
element.__data__.inputs.forEach(function(edge) {
for (const n of graph.state.selection) {
n.inputs.forEach(function (edge) {
if (edge.source.visible && edge.target.visible) {
edge.visible = true;
}
});
element.__data__.outputs.forEach(function(edge) {
n.outputs.forEach(function (edge) {
if (edge.source.visible && edge.target.visible) {
edge.visible = true;
}
});
});
}
}
updateInputAndOutputBubbles() {
var g = this;
var s = g.visibleBubbles;
s.classed("filledBubbleStyle", function(c) {
s.classed("filledBubbleStyle", function (c) {
var components = this.id.split(',');
if (components[0] == "ib") {
var edge = g.nodeMap[components[3]].inputs[components[2]];
......@@ -343,7 +330,7 @@ class GraphView extends View {
} else {
return g.nodeMap[components[1]].areAnyOutputsVisible() == 2;
}
}).classed("halfFilledBubbleStyle", function(c) {
}).classed("halfFilledBubbleStyle", function (c) {
var components = this.id.split(',');
if (components[0] == "ib") {
var edge = g.nodeMap[components[3]].inputs[components[2]];
......@@ -351,7 +338,7 @@ class GraphView extends View {
} else {
return g.nodeMap[components[1]].areAnyOutputsVisible() == 1;
}
}).classed("bubbleStyle", function(c) {
}).classed("bubbleStyle", function (c) {
var components = this.id.split(',');
if (components[0] == "ib") {
var edge = g.nodeMap[components[3]].inputs[components[2]];
......@@ -360,7 +347,7 @@ class GraphView extends View {
return g.nodeMap[components[1]].areAnyOutputsVisible() == 0;
}
});
s.each(function(c) {
s.each(function (c) {
var components = this.id.split(',');
if (components[0] == "ob") {
var from = g.nodeMap[components[1]];
......@@ -373,29 +360,26 @@ class GraphView extends View {
}
attachSelection(s) {
var graph = this;
if (s.size != 0) {
this.visibleNodes.each(function(n) {
if (s.has(this.__data__.id)) {
graph.state.selection.select(this, true);
}
});
}
const graph = this;
if (!(s instanceof Set)) return;
graph.selectionHandler.clear();
const selected = graph.nodes.filter((n) =>
s.has(graph.state.selection.stringKey(n)) && (!graph.state.hideDead || n.isLive()));
graph.selectionHandler.select(selected, true);
}
detachSelection() {
var selection = this.state.selection.detachSelection();
var s = new Set();
for (var i of selection) {
s.add(i.__data__.id);
};
return s;
return this.state.selection.detachSelection();
}
pathMouseDown(path, d) {
pathMouseUp(path, d) {
d3.event.stopPropagation();
this.state.selection.clear();
this.state.selection.add(path);
const edge = path.datum();
if (!d3.event.shiftKey) {
this.selectionHandler.clear();
}
this.selectionHandler.select([edge.source, edge.target], true);
return false;
};
nodeMouseDown(node, d) {
......@@ -404,62 +388,35 @@ class GraphView extends View {
}
nodeMouseUp(d3node, d) {
var graph = this,
state = graph.state,
consts = graph.consts;
var mouseDownNode = state.mouseDownNode;
let graph = this;
let state = graph.state;
if (!mouseDownNode) return;
if (!state.mouseDownNode) return;
if (state.justDragged) {
// dragged, not clicked
redetermineGraphBoundingBox(graph);
state.justDragged = false;
} else{
} else {
// clicked, not dragged
var extend = d3.event.shiftKey;
var selection = graph.state.selection;
if (!extend) {
selection.clear();
graph.selectionHandler.clear();
}
selection.select(d3node[0][0], true);
graph.selectionHandler.select([d3node.datum()], undefined);
}
}
selectSourcePositions(start, end, selected) {
var graph = this;
var map = [];
var sel = graph.nodes.filter(function(n) {
var pos = (n.pos === undefined)
? -1
: n.getFunctionRelativeSourcePosition(graph);
if (pos >= start && pos < end) {
map[n.id] = true;
n.visible = true;
}
});
graph.updateGraphVisibility();
graph.visibleNodes.filter(function(n) { return map[n.id]; })
.each(function(n) {
var selection = graph.state.selection;
selection.select(d3.select(this), selected);
});
}
selectAllNodes(inEdges, filter) {
var graph = this;
if (!d3.event.shiftKey) {
graph.state.selection.clear();
}
graph.state.selection.select(graph.visibleNodes[0], true);
const allVisibleNodes = graph.nodes.filter((n) => n.visible);
graph.state.selection.select(allVisibleNodes, true);
graph.updateGraphVisibility();
}
uploadAction(graph) {
document.getElementById("hidden-file-upload").click();
}
layoutAction(graph) {
graph.updateGraphVisibility();
graph.layoutGraph();
......@@ -468,36 +425,50 @@ class GraphView extends View {
}
showAllAction(graph) {
graph.nodes.filter(function(n) { n.visible = true; })
graph.edges.filter(function(e) { e.visible = true; })
graph.nodes.forEach(function (n) {
n.visible = !graph.state.hideDead || n.isLive();
});
graph.edges.forEach(function (e) {
e.visible = !graph.state.hideDead || (e.source.isLive() && e.target.isLive());
});
graph.updateGraphVisibility();
graph.viewWholeGraph();
}
hideDeadAction(graph) {
graph.nodes.filter(function(n) { if (!n.isLive()) n.visible = false; })
toggleHideDead(graph) {
graph.state.hideDead = !graph.state.hideDead;
if (graph.state.hideDead) graph.hideDead();
var element = document.getElementById('toggle-hide-dead');
element.classList.toggle('button-input-toggled', graph.state.hideDead);
}
hideDead() {
const graph = this;
graph.nodes.filter(function (n) {
if (!n.isLive()) {
n.visible = false;
graph.state.selection.select([n], false);
}
})
graph.updateGraphVisibility();
}
hideUnselectedAction(graph) {
var unselected = graph.visibleNodes.filter(function(n) {
return !this.classList.contains("selected");
});
unselected.each(function(n) {
n.visible = false;
graph.nodes.forEach(function (n) {
if (!graph.state.selection.isSelected(n)) {
n.visible = false;
}
});
graph.updateGraphVisibility();
}
hideSelectedAction(graph) {
var selected = graph.visibleNodes.filter(function(n) {
return this.classList.contains("selected");
});
selected.each(function(n) {
n.visible = false;
graph.nodes.forEach(function (n) {
if (graph.state.selection.isSelected(n)) {
n.visible = false;
}
});
graph.state.selection.clear();
graph.updateGraphVisibility();
graph.selectionHandler.clear();
}
zoomSelectionAction(graph) {
......@@ -508,34 +479,33 @@ class GraphView extends View {
graph.toggleTypes();
}
searchInputAction(graph) {
searchInputAction(graph, searchBar) {
if (d3.event.keyCode == 13) {
graph.state.selection.clear();
var query = this.value;
graph.selectionHandler.clear();
var query = searchBar.value;
window.sessionStorage.setItem("lastSearch", query);
if (query.length == 0) return;
var reg = new RegExp(query);
var filterFunction = function(n) {
var filterFunction = function (n) {
return (reg.exec(n.getDisplayLabel()) != null ||
(graph.state.showTypes && reg.exec(n.getDisplayType())) ||
reg.exec(n.opcode) != null);
(graph.state.showTypes && reg.exec(n.getDisplayType())) ||
reg.exec(n.opcode) != null);
};
if (d3.event.ctrlKey) {
graph.nodes.forEach(function(n, i) {
if (filterFunction(n)) {
n.visible = true;
const selection = graph.nodes.filter(
function (n, i) {
if ((d3.event.ctrlKey || n.visible) && filterFunction(n)) {
if (d3.event.ctrlKey) n.visible = true;
return true;
}
return false;
});
graph.updateGraphVisibility();
}
var selected = graph.visibleNodes.each(function(n) {
if (filterFunction(n)) {
graph.state.selection.select(this, true);
}
});
graph.selectionHandler.select(selection, true);
graph.connectVisibleSelectedNodes();
graph.updateGraphVisibility();
this.blur();
searchBar.blur();
graph.viewSelection();
}
d3.event.stopPropagation();
......@@ -546,15 +516,15 @@ class GraphView extends View {
}
svgMouseUp() {
var graph = this,
state = graph.state;
const graph = this;
const state = graph.state;
if (state.justScaleTransGraph) {
// Dragged
state.justScaleTransGraph = false;
} else {
// Clicked
if (state.mouseDownNode == null) {
graph.state.selection.clear();
if (state.mouseDownNode == null && !d3.event.shiftKey) {
graph.selectionHandler.clear();
}
}
state.mouseDownNode = null;
......@@ -566,16 +536,16 @@ class GraphView extends View {
var graph = this;
// Don't handle key press repetition
if(state.lastKeyDown !== -1) return;
if (state.lastKeyDown !== -1) return;
var showSelectionFrontierNodes = function(inEdges, filter, select) {
var frontier = graph.getNodeFrontier(state.selection.selection, inEdges, filter);
var showSelectionFrontierNodes = function (inEdges, filter, select) {
var frontier = graph.getNodeFrontier(state.selection, inEdges, filter);
if (frontier != undefined) {
if (select) {
if (!d3.event.shiftKey) {
state.selection.clear();
}
state.selection.select(frontier[0], true);
state.selection.select(frontier, true);
}
graph.updateGraphVisibility();
}
......@@ -584,81 +554,81 @@ class GraphView extends View {
var allowRepetition = true;
var eventHandled = true; // unless the below switch defaults
switch(d3.event.keyCode) {
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// '1'-'9'
showSelectionFrontierNodes(true,
switch (d3.event.keyCode) {
case 49:
case 50:
case 51:
case 52:
case 53:
case 54:
case 55:
case 56:
case 57:
// '1'-'9'
showSelectionFrontierNodes(true,
(edge, index) => { return index == (d3.event.keyCode - 49); },
false);
break;
case 97:
case 98:
case 99:
case 100:
case 101:
case 102:
case 103:
case 104:
case 105:
// 'numpad 1'-'numpad 9'
showSelectionFrontierNodes(true,
break;
case 97:
case 98:
case 99:
case 100:
case 101:
case 102:
case 103:
case 104:
case 105:
// 'numpad 1'-'numpad 9'
showSelectionFrontierNodes(true,
(edge, index) => { return index == (d3.event.keyCode - 97); },
false);
break;
case 67:
// 'c'
showSelectionFrontierNodes(true,
break;
case 67:
// 'c'
showSelectionFrontierNodes(!d3.event.altKey,
(edge, index) => { return edge.type == 'control'; },
false);
break;
case 69:
// 'e'
showSelectionFrontierNodes(true,
(edge, index) => { return edge.type == 'effect'; },
false);
break;
case 79:
// 'o'
showSelectionFrontierNodes(false, undefined, false);
break;
case 73:
// 'i'
showSelectionFrontierNodes(true, undefined, false);
break;
case 65:
// 'a'
graph.selectAllNodes();
allowRepetition = false;
break;
case 38:
case 40: {
showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true);
break;
}
case 82:
// 'r'
if (!d3.event.ctrlKey) {
this.layoutAction(this);
} else {
eventHandled = false;
true);
break;
case 69:
// 'e'
showSelectionFrontierNodes(!d3.event.altKey,
(edge, index) => { return edge.type == 'effect'; },
true);
break;
case 79:
// 'o'
showSelectionFrontierNodes(false, undefined, false);
break;
case 73:
// 'i'
showSelectionFrontierNodes(true, undefined, false);
break;
case 65:
// 'a'
graph.selectAllNodes();
allowRepetition = false;
break;
case 38:
case 40: {
showSelectionFrontierNodes(d3.event.keyCode == 38, undefined, true);
break;
}
break;
case 191:
// '/'
document.getElementById("search-input").focus();
document.getElementById("search-input").select();
break;
default:
eventHandled = false;
break;
case 82:
// 'r'
if (!d3.event.ctrlKey) {
this.layoutAction(this);
} else {
eventHandled = false;
}
break;
case 191:
// '/'
document.getElementById("search-input").focus();
document.getElementById("search-input").select();
break;
default:
eventHandled = false;
break;
}
if (eventHandled) {
d3.event.preventDefault();
......@@ -672,127 +642,122 @@ class GraphView extends View {
this.state.lastKeyDown = -1
};
layoutEdges() {
var graph = this;
graph.maxGraphX = graph.maxGraphNodeX;
this.visibleEdges.attr("d", function(edge){
return edge.generatePath(graph);
});
}
layoutGraph() {
layoutNodeGraph(this);
}
// call to propagate changes to graph
updateGraphVisibility() {
let graph = this;
let state = graph.state;
var graph = this,
state = graph.state;
var filteredEdges = graph.edges.filter(function(e) { return e.isVisible(); });
var visibleEdges = graph.visibleEdges.data(filteredEdges, function(edge) {
var filteredEdges = graph.edges.filter(function (e) {
return e.isVisible();
});
const selEdges = graph.visibleEdges.selectAll("path").data(filteredEdges, function (edge) {
return edge.stringID();
});
// remove old links
selEdges.exit().remove();
// add new paths
visibleEdges.enter()
selEdges.enter()
.append('path')
.style('marker-end','url(#end-arrow)')
.classed('hidden', function(e) {
.style('marker-end', 'url(#end-arrow)')
.classed('hidden', function (e) {
return !e.isVisible();
})
.attr("id", function(edge){ return "e," + edge.stringID(); })
.on("mousedown", function(d){
graph.pathMouseDown.call(graph, d3.select(this), d);
.attr("id", function (edge) { return "e," + edge.stringID(); })
.on("mouseup", function (d) {
graph.pathMouseUp.call(graph, d3.select(this), d);
})
.attr("adjacentToHover", "false");
// Set the correct styles on all of the paths
visibleEdges.classed('value', function(e) {
selEdges.classed('value', function (e) {
return e.type == 'value' || e.type == 'context';
}).classed('control', function(e) {
}).classed('control', function (e) {
return e.type == 'control';
}).classed('effect', function(e) {
}).classed('effect', function (e) {
return e.type == 'effect';
}).classed('frame-state', function(e) {
}).classed('frame-state', function (e) {
return e.type == 'frame-state';
}).attr('stroke-dasharray', function(e) {
}).attr('stroke-dasharray', function (e) {
if (e.type == 'frame-state') return "10,10";
return (e.type == 'effect') ? "5,5" : "";
});
// remove old links
visibleEdges.exit().remove();
graph.visibleEdges = visibleEdges;
// update existing nodes
var filteredNodes = graph.nodes.filter(function(n) { return n.visible; });
graph.visibleNodes = graph.visibleNodes.data(filteredNodes, function(d) {
// select existing nodes
var filteredNodes = graph.nodes.filter(function (n) {
return n.visible;
});
let selNodes = graph.visibleNodes.selectAll("g").data(filteredNodes, function (d) {
return d.id;
});
graph.visibleNodes.attr("transform", function(n){
return "translate(" + n.x + "," + n.y + ")";
}).select('rect').
attr(HEIGHT, function(d) { return graph.getNodeHeight(d); });
// remove old nodes
selNodes.exit().remove();
// add new nodes
var newGs = graph.visibleNodes.enter()
var newGs = selNodes.enter()
.append("g");
newGs.classed("turbonode", function(n) { return true; })
.classed("control", function(n) { return n.isControl(); })
.classed("live", function(n) { return n.isLive(); })
.classed("dead", function(n) { return !n.isLive(); })
.classed("javascript", function(n) { return n.isJavaScript(); })
.classed("input", function(n) { return n.isInput(); })
.classed("simplified", function(n) { return n.isSimplified(); })
.classed("machine", function(n) { return n.isMachine(); })
.attr("transform", function(d){ return "translate(" + d.x + "," + d.y + ")";})
.on("mousedown", function(d){
newGs.classed("turbonode", function (n) { return true; })
.classed("control", function (n) { return n.isControl(); })
.classed("live", function (n) { return n.isLive(); })
.classed("dead", function (n) { return !n.isLive(); })
.classed("javascript", function (n) { return n.isJavaScript(); })
.classed("input", function (n) { return n.isInput(); })
.classed("simplified", function (n) { return n.isSimplified(); })
.classed("machine", function (n) { return n.isMachine(); })
.on("mousedown", function (d) {
graph.nodeMouseDown.call(graph, d3.select(this), d);
})
.on("mouseup", function(d){
.on("mouseup", function (d) {
graph.nodeMouseUp.call(graph, d3.select(this), d);
})
.on('mouseover', function(d){
.on('mouseover', function (d) {
var nodeSelection = d3.select(this);
let node = graph.nodeMap[d.id];
let adjInputEdges = graph.visibleEdges.filter(e => { return e.target === node; });
let adjOutputEdges = graph.visibleEdges.filter(e => { return e.source === node; });
let visibleEdges = graph.visibleEdges.selectAll('path');
let adjInputEdges = visibleEdges.filter(e => { return e.target === node; });
let adjOutputEdges = visibleEdges.filter(e => { return e.source === node; });
adjInputEdges.attr('relToHover', "input");
adjOutputEdges.attr('relToHover', "output");
let adjInputNodes = adjInputEdges.data().map(e => e.source);
graph.visibleNodes.data(adjInputNodes, function(d) {
let visibleNodes = graph.visibleNodes.selectAll("g");
visibleNodes.data(adjInputNodes, function (d) {
return d.id;
}).attr('relToHover', "input");
let adjOutputNodes = adjOutputEdges.data().map(e => e.target);
graph.visibleNodes.data(adjOutputNodes, function(d) {
visibleNodes.data(adjOutputNodes, function (d) {
return d.id;
}).attr('relToHover', "output");
graph.updateGraphVisibility();
})
.on('mouseout', function(d){
.on('mouseout', function (d) {
var nodeSelection = d3.select(this);
let node = graph.nodeMap[d.id];
let adjEdges = graph.visibleEdges.filter(e => { return e.target === node || e.source === node; });
let visibleEdges = graph.visibleEdges.selectAll('path');
let adjEdges = visibleEdges.filter(e => { return e.target === node || e.source === node; });
adjEdges.attr('relToHover', "none");
let adjNodes = adjEdges.data().map(e => e.target).concat(adjEdges.data().map(e => e.source));
let nodes = graph.visibleNodes.data(adjNodes, function(d) {
let visibleNodes = graph.visibleNodes.selectAll("g");
let nodes = visibleNodes.data(adjNodes, function (d) {
return d.id;
}).attr('relToHover', "none");
graph.updateGraphVisibility();
})
.call(graph.drag);
.call(graph.drag)
newGs.append("rect")
.attr("rx", 10)
.attr("ry", 10)
.attr(WIDTH, function(d) {
.attr(WIDTH, function (d) {
return d.getTotalNodeWidth();
})
.attr(HEIGHT, function(d) {
.attr(HEIGHT, function (d) {
return graph.getNodeHeight(d);
})
......@@ -801,18 +766,18 @@ class GraphView extends View {
var x = d.getInputX(i);
var y = -DEFAULT_NODE_BUBBLE_RADIUS;
var s = g.append('circle')
.classed("filledBubbleStyle", function(c) {
.classed("filledBubbleStyle", function (c) {
return d.inputs[i].isVisible();
} )
.classed("bubbleStyle", function(c) {
})
.classed("bubbleStyle", function (c) {
return !d.inputs[i].isVisible();
} )
})
.attr("id", "ib," + d.inputs[i].stringID())
.attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
.attr("transform", function(d) {
.attr("transform", function (d) {
return "translate(" + x + "," + y + ")";
})
.on("mousedown", function(d){
.on("mousedown", function (d) {
var components = this.id.split(',');
var node = graph.nodeMap[components[3]];
var edge = node.inputs[components[2]];
......@@ -826,21 +791,21 @@ class GraphView extends View {
var x = d.getOutputX();
var y = graph.getNodeHeight(d) + DEFAULT_NODE_BUBBLE_RADIUS;
var s = g.append('circle')
.classed("filledBubbleStyle", function(c) {
.classed("filledBubbleStyle", function (c) {
return d.areAnyOutputsVisible() == 2;
} )
.classed("halFilledBubbleStyle", function(c) {
})
.classed("halFilledBubbleStyle", function (c) {
return d.areAnyOutputsVisible() == 1;
} )
.classed("bubbleStyle", function(c) {
})
.classed("bubbleStyle", function (c) {
return d.areAnyOutputsVisible() == 0;
} )
})
.attr("id", "ob," + d.id)
.attr("r", DEFAULT_NODE_BUBBLE_RADIUS)
.attr("transform", function(d) {
.attr("transform", function (d) {
return "translate(" + x + "," + y + ")";
})
.on("mousedown", function(d) {
.on("mousedown", function (d) {
d.setOutputVisibility(d.areAnyOutputsVisible() == 0);
d3.event.stopPropagation();
graph.updateGraphVisibility();
......@@ -848,54 +813,63 @@ class GraphView extends View {
}
}
newGs.each(function(d){
newGs.each(function (d) {
appendInputAndOutputBubbles(d3.select(this), d);
});
newGs.each(function(d){
newGs.each(function (d) {
d3.select(this).append("text")
.classed("label", true)
.attr("text-anchor","right")
.attr("text-anchor", "right")
.attr("dx", 5)
.attr("dy", 5)
.append('tspan')
.text(function(l) {
.text(function (l) {
return d.getDisplayLabel();
})
.append("title")
.text(function(l) {
.text(function (l) {
return d.getTitle();
})
if (d.type != undefined) {
d3.select(this).append("text")
.classed("label", true)
.classed("type", true)
.attr("text-anchor","right")
.attr("text-anchor", "right")
.attr("dx", 5)
.attr("dy", d.labelbbox.height + 5)
.append('tspan')
.text(function(l) {
.text(function (l) {
return d.getDisplayType();
})
.append("title")
.text(function(l) {
.text(function (l) {
return d.getType();
})
}
});
graph.visibleNodes.select('.type').each(function (d) {
selNodes.select('.type').each(function (d) {
this.setAttribute('visibility', graph.state.showTypes ? 'visible' : 'hidden');
});
// remove old nodes
graph.visibleNodes.exit().remove();
selNodes
.classed("selected", function (n) {
if (state.selection.isSelected(n)) return true;
return false;
})
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
.select('rect')
.attr(HEIGHT, function (d) { return graph.getNodeHeight(d); });
graph.visibleBubbles = d3.selectAll('circle');
graph.updateInputAndOutputBubbles();
graph.layoutEdges();
graph.maxGraphX = graph.maxGraphNodeX;
selEdges.attr("d", function (edge) {
return edge.generatePath(graph);
});
graph.svg.style.height = '100%';
}
......@@ -957,12 +931,12 @@ class GraphView extends View {
translateClipped(translate, scale, transition) {
var graph = this;
var graphNode = this.graphElement[0][0];
var graphNode = this.graphElement.node();
var translate = this.getVisibleTranslation(translate, scale);
if (transition) {
graphNode.classList.add('visible-transition');
clearTimeout(graph.transitionTimout);
graph.transitionTimout = setTimeout(function(){
graph.transitionTimout = setTimeout(function () {
graphNode.classList.remove('visible-transition');
}, 1000);
}
......@@ -972,15 +946,15 @@ class GraphView extends View {
graph.dragSvg.scale(scale);
}
zoomed(){
zoomed() {
this.state.justScaleTransGraph = true;
var scale = this.dragSvg.scale();
var scale = this.dragSvg.scale();
this.translateClipped(d3.event.translate, scale);
}
getSvgViewDimensions() {
var canvasWidth = this.parentNode.clientWidth;
var canvasWidth = this.container.clientWidth;
var documentElement = document.documentElement;
var canvasHeight = documentElement.clientHeight;
return [canvasWidth, canvasHeight];
......@@ -1002,7 +976,6 @@ class GraphView extends View {
}
fitGraphViewToWindow() {
this.svg.attr("height", document.documentElement.clientHeight + "px");
this.translateClipped(this.dragSvg.translate(), this.dragSvg.scale());
}
......@@ -1010,11 +983,7 @@ class GraphView extends View {
var graph = this;
graph.state.showTypes = !graph.state.showTypes;
var element = document.getElementById('toggle-types');
if (graph.state.showTypes) {
element.classList.add('button-input-toggled');
} else {
element.classList.remove('button-input-toggled');
}
element.classList.toggle('button-input-toggled', graph.state.showTypes);
graph.updateGraphVisibility();
}
......@@ -1022,8 +991,8 @@ class GraphView extends View {
var graph = this;
var minX, maxX, minY, maxY;
var hasSelection = false;
graph.visibleNodes.each(function(n) {
if (this.classList.contains("selected")) {
graph.visibleNodes.selectAll("g").each(function (n) {
if (graph.state.selection.isSelected(n)) {
hasSelection = true;
minX = minX ? Math.min(minX, n.x) : n.x;
maxX = maxX ? Math.max(maxX, n.x + n.getTotalNodeWidth()) :
......@@ -1035,8 +1004,8 @@ class GraphView extends View {
});
if (hasSelection) {
graph.viewGraphRegion(minX - NODE_INPUT_WIDTH, minY - 60,
maxX + NODE_INPUT_WIDTH, maxY + 60,
true);
maxX + NODE_INPUT_WIDTH, maxY + 60,
true);
}
}
......@@ -1048,7 +1017,7 @@ class GraphView extends View {
var scale = Math.min(dimensions[0] / width, dimensions[1] / height);
scale = Math.min(1.5, scale);
scale = Math.max(graph.minScale(), scale);
var translation = [-minX*scale, -minY*scale];
var translation = [-minX * scale, -minY * scale];
translation = graph.getVisibleTranslation(translation, scale);
graph.translateClipped(translation, scale, transition);
}
......
......@@ -3,13 +3,11 @@
<head>
<title>Turbolizer</title>
<link rel="stylesheet" href="turbo-visualizer.css" />
<link rel="stylesheet" href="prettify.css" />
<link rel="icon" type="image/png" href="turbolizer.png">
</head>
<body>
<div id="left" class="viewpane">
<div id='source-text'>
<pre id='source-text-pre'\>
</div>
</div>
<div id="left" class="viewpane scrollable"></div>
<div class="resizer-left"></div>
<div id="middle" class="viewpane">
<div id="graph-toolbox-anchor">
......@@ -18,7 +16,7 @@
alt="layout graph" class="button-input">
<input id="show-all" type="image" title="show all nodes" src="expand-all.jpg"
alt="show all nodes" class="button-input">
<input id="hide-dead" type="image" title="only live nodes" src="live.png"
<input id="toggle-hide-dead" type="image" title="show only live nodes" src="live.png"
alt="only live nodes" class="button-input">
<input id="hide-unselected" type="image" title="hide unselected nodes"
src="hide-unselected.png" alt="hide unselected nodes" class="button-input">
......@@ -36,33 +34,17 @@
</select>
</span>
</div>
<div id="load-file">
<input type="file" id="hidden-file-upload">
<input id="upload-helper" type="file">
<input id="upload" type="image" title="load graph" class="button-input"
src="upload-icon.png" alt="upload graph">
</div>
<div id="empty" width="100%" height="100%"></div>
<div id="graph" width="100%" height="100%"></div>
<div id="schedule" width="100%">
<pre id="schedule-text-pre" class='prettyprint prettyprinted'>
<ul id="schedule-list" class='nolinenums noindent'>
</ul>
</pre>
</div>
<div id='text-placeholder' width="0px" height="0px" style="position: absolute; top:100000px;" ><svg><text text-anchor="right">
<tspan white-space="inherit" id="text-measure"/>
</text></svg></div>
</div>
<div class="resizer-right"></div>
<div id="right" class="viewpane">
<div id='disassembly'>
<pre id='disassembly-text-pre' class='prettyprint prettyprinted'>
<ul id='disassembly-list' class='nolinenums noindent'>
</ul>
</pre>
</div>
</div>
<div id="right" class="viewpane scrollable"></div>
<div id="source-collapse" class="collapse-pane">
<input id="source-expand" type="image" title="show source"
src="right-arrow.png" class="button-input invisible">
......@@ -76,19 +58,18 @@
src="right-arrow.png" class="button-input">
</div>
<script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js"></script>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="https://cdn.jsdelivr.net/filesaver.js/0.1/FileSaver.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="monkey.js"></script>
<script src="util.js"></script>
<script src="lang-disassembly.js"></script>
<script src="node.js"></script>
<script src="edge.js"></script>
<script src="source-resolver.js"></script>
<script src="selection.js"></script>
<script src="selection-broker.js"></script>
<script src="constants.js"></script>
<script src="view.js"></script>
<script src="text-view.js"></script>
<script src="empty-view.js"></script>
<script src="code-view.js"></script>
<script src="graph-layout.js"></script>
<script src="graph-view.js"></script>
......
......@@ -5,124 +5,159 @@
"use strict";
class ScheduleView extends TextView {
constructor(id, broker) {
super(id, broker, null, false);
let view = this;
let BLOCK_STYLE = {
css: 'tag'
};
const BLOCK_HEADER_STYLE = {
css: 'com',
block_id: -1,
location: function(text) {
let matches = /\d+/.exec(text);
if (!matches) return undefined;
BLOCK_HEADER_STYLE.block_id = Number(matches[0]);
return {
block_id: BLOCK_HEADER_STYLE.block_id
};
},
};
const BLOCK_LINK_STYLE = {
css: 'tag',
link: function(text) {
let id = Number(text.substr(1));
view.select(function(location) { return location.block_id == id; }, true, true);
}
};
const ID_STYLE = {
css: 'tag',
location: function(text) {
let matches = /\d+/.exec(text);
return {
node_id: Number(matches[0]),
block_id: BLOCK_HEADER_STYLE.block_id
};
},
};
const ID_LINK_STYLE = {
css: 'tag',
link: function(text) {
let id = Number(text);
view.select(function(location) { return location.node_id == id; }, true, true);
createViewElement() {
const pane = document.createElement('div');
pane.setAttribute('id', "schedule");
pane.innerHTML =
`<pre id='schedule-text-pre' class='prettyprint prettyprinted'>
<ul id='schedule-list' class='nolinenums noindent'>
</ul>
</pre>`;
return pane;
}
constructor(parentId, broker) {
super(parentId, broker, null, false);
}
attachSelection(s) {
const view = this;
if (!(s instanceof Set)) return;
view.selectionHandler.clear();
view.blockSelectionHandler.clear();
view.sourcePositionSelectionHandler.clear();
const selected = new Array();
for (const key of s) selected.push(key);
view.selectionHandler.select(selected, true);
}
createElementFromString(htmlString) {
var div = document.createElement('div');
div.innerHTML = htmlString.trim();
return div.firstChild;
}
elementForBlock(block) {
const view = this;
function createElement(tag, cls, content) {
const el = document.createElement(tag);
if (isIterable(cls)) {
for (const c of cls) el.classList.add(c);
} else {
el.classList.add(cls);
}
};
const NODE_STYLE = { css: 'kwd' };
const GOTO_STYLE = { css: 'kwd',
goto_id: -2,
location: function(text) {
return {
node_id: GOTO_STYLE.goto_id--,
block_id: BLOCK_HEADER_STYLE.block_id
};
if (content != undefined) el.innerHTML = content;
return el;
}
function mkNodeLinkHandler(nodeId) {
return function (e) {
e.stopPropagation();
if (!e.shiftKey) {
view.selectionHandler.clear();
}
view.selectionHandler.select([nodeId], true);
};
}
function createElementForNode(node) {
const nodeEl = createElement("div", "node");
const node_id = createElement("div", ["node-id", "tag", "clickable"], node.id);
node_id.onclick = mkNodeLinkHandler(node.id);
view.addHtmlElementForNodeId(node.id, node_id);
nodeEl.appendChild(node_id);
const node_label = createElement("div", "node-label", node.label);
nodeEl.appendChild(node_label);
if (node.inputs.length > 0) {
const node_parameters = createElement("div", ["parameter-list", "comma-sep-list"]);
for (const param of node.inputs) {
const paramEl = createElement("div", ["parameter", "tag", "clickable"], param);
node_parameters.appendChild(paramEl);
paramEl.onclick = mkNodeLinkHandler(param);
view.addHtmlElementForNodeId(param, paramEl);
}
nodeEl.appendChild(node_parameters);
}
return nodeEl;
}
const ARROW_STYLE = { css: 'kwd' };
let patterns = [
[
[/^--- BLOCK B\d+/, BLOCK_HEADER_STYLE, 1],
[/^\s+\d+: /, ID_STYLE, 2],
[/^\s+Goto/, GOTO_STYLE, 6],
[/^.*/, null, -1]
],
[
[/^ +/, null],
[/^\(deferred\)/, BLOCK_HEADER_STYLE],
[/^B\d+/, BLOCK_LINK_STYLE],
[/^<-/, ARROW_STYLE],
[/^->/, ARROW_STYLE],
[/^,/, null],
[/^---/, BLOCK_HEADER_STYLE, -1]
],
// Parse opcode including []
[
[/^[A-Za-z0-9_]+(\[.*\])?$/, NODE_STYLE, -1],
[/^[A-Za-z0-9_]+(\[(\[.*?\]|.)*?\])?/, NODE_STYLE, 3]
],
// Parse optional parameters
[
[/^ /, null, 4],
[/^\(/, null],
[/^\d+/, ID_LINK_STYLE],
[/^, /, null],
[/^\)$/, null, -1],
[/^\)/, null, 4],
],
[
[/^ -> /, ARROW_STYLE, 5],
[/^.*/, null, -1]
],
[
[/^B\d+$/, BLOCK_LINK_STYLE, -1],
[/^B\d+/, BLOCK_LINK_STYLE],
[/^, /, null]
],
[
[/^ -> /, ARROW_STYLE],
[/^B\d+$/, BLOCK_LINK_STYLE, -1]
]
];
this.setPatterns(patterns);
function mkBlockLinkHandler(blockId) {
return function (e) {
e.stopPropagation();
if (!e.shiftKey) {
view.blockSelectionHandler.clear();
}
view.blockSelectionHandler.select(["" + blockId], true);
};
}
const schedule_block = createElement("div", "schedule-block");
const block_id = createElement("div", ["block-id", "com", "clickable"], block.id);
block_id.onclick = mkBlockLinkHandler(block.id);
schedule_block.appendChild(block_id);
const block_pred = createElement("div", ["predecessor-list", "block-list", "comma-sep-list"]);
for (const pred of block.pred) {
const predEl = createElement("div", ["block-id", "com", "clickable"], pred);
predEl.onclick = mkBlockLinkHandler(pred);
block_pred.appendChild(predEl);
}
if (block.pred.length) schedule_block.appendChild(block_pred);
const nodes = createElement("div", "nodes");
for (const node of block.nodes) {
nodes.appendChild(createElementForNode(node, block.id));
}
schedule_block.appendChild(nodes);
const block_succ = createElement("div", ["successor-list", "block-list", "comma-sep-list"]);
for (const succ of block.succ) {
const succEl = createElement("div", ["block-id", "com", "clickable"], succ);
succEl.onclick = mkBlockLinkHandler(succ);
block_succ.appendChild(succEl);
}
if (block.succ.length) schedule_block.appendChild(block_succ);
this.addHtmlElementForBlockId(block.id, schedule_block);
return schedule_block;
}
initializeContent(data, rememberedSelection) {
super.initializeContent(data, rememberedSelection);
var graph = this;
var locations = [];
for (var id of rememberedSelection) {
locations.push({ node_id : id });
addBlocks(blocks) {
for (const block of blocks) {
const blockEl = this.elementForBlock(block);
this.divNode.appendChild(blockEl);
}
this.selectLocations(locations, true, true);
}
initializeContent(data, rememberedSelection) {
this.clearText();
this.schedule = data.schedule
this.addBlocks(data.schedule.blocks);
this.attachSelection(rememberedSelection);
}
detachSelection() {
var selection = this.selection.detachSelection();
var s = new Set();
for (var i of selection) {
if (i.location.node_id != undefined && i.location.node_id > 0) {
s.add(i.location.node_id);
this.blockSelection.clear();
this.sourcePositionSelection.clear();
return this.selection.detachSelection();
}
lineString(node) {
return `${node.id}: ${node.label}(${node.inputs.join(", ")})`
}
searchInputAction(view, searchBar) {
d3.event.stopPropagation();
this.selectionHandler.clear();
const query = searchBar.value;
if (query.length == 0) return;
const select = [];
window.sessionStorage.setItem("lastSearch", query);
const reg = new RegExp(query);
for (const node of this.schedule.nodes) {
if (node === undefined) continue;
if (reg.exec(this.lineString(node)) != null) {
select.push(node.id)
}
};
return s;
}
this.selectionHandler.select(select, true);
}
}
......@@ -2,98 +2,72 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var SelectionBroker = function() {
this.brokers = [];
this.dispatching = false;
this.lastDispatchingHandler = null;
this.nodePositionMap = [];
this.sortedPositionList = [];
this.positionNodeMap = [];
};
class SelectionBroker {
constructor(sourceResolver) {
this.sourcePositionHandlers = [];
this.nodeHandlers = [];
this.blockHandlers = [];
this.sourceResolver = sourceResolver;
};
SelectionBroker.prototype.addSelectionHandler = function(handler) {
this.brokers.push(handler);
}
addSourcePositionHandler(handler) {
this.sourcePositionHandlers.push(handler);
}
SelectionBroker.prototype.setNodePositionMap = function(map) {
let broker = this;
if (!map) return;
broker.nodePositionMap = map;
broker.positionNodeMap = [];
broker.sortedPositionList = [];
let next = 0;
for (let i in broker.nodePositionMap) {
broker.sortedPositionList[next] = Number(broker.nodePositionMap[i]);
broker.positionNodeMap[next++] = i;
addNodeHandler(handler) {
this.nodeHandlers.push(handler);
}
broker.sortedPositionList = sortUnique(broker.sortedPositionList,
function(a,b) { return a - b; });
this.positionNodeMap.sort(function(a,b) {
let result = broker.nodePositionMap[a] - broker.nodePositionMap[b];
if (result != 0) return result;
return a - b;
});
}
SelectionBroker.prototype.select = function(from, locations, selected) {
let broker = this;
if (!broker.dispatching) {
broker.lastDispatchingHandler = from;
try {
broker.dispatching = true;
let enrichLocations = function(locations) {
result = [];
for (let location of locations) {
let newLocation = {};
if (location.pos_start != undefined) {
newLocation.pos_start = location.pos_start;
}
if (location.pos_end != undefined) {
newLocation.pos_end = location.pos_end;
}
if (location.node_id != undefined) {
newLocation.node_id = location.node_id;
}
if (location.block_id != undefined) {
newLocation.block_id = location.block_id;
}
if (newLocation.pos_start == undefined &&
newLocation.pos_end == undefined &&
newLocation.node_id != undefined) {
if (broker.nodePositionMap && broker.nodePositionMap[location.node_id]) {
newLocation.pos_start = broker.nodePositionMap[location.node_id];
newLocation.pos_end = location.pos_start + 1;
}
}
result.push(newLocation);
}
return result;
}
locations = enrichLocations(locations);
for (var b of this.brokers) {
if (b != from) {
b.brokeredSelect(locations, selected);
}
addBlockHandler(handler) {
this.blockHandlers.push(handler);
}
broadcastSourcePositionSelect(from, sourcePositions, selected) {
let broker = this;
sourcePositions = sourcePositions.filter((l) => {
if (typeof l.scriptOffset == 'undefined'
|| typeof l.inliningId == 'undefined') {
console.log("Warning: invalid source position");
return false;
}
return true;
});
for (var b of this.sourcePositionHandlers) {
if (b != from) b.brokeredSourcePositionSelect(sourcePositions, selected);
}
finally {
broker.dispatching = false;
const nodes = this.sourceResolver.sourcePositionsToNodeIds(sourcePositions);
for (var b of this.nodeHandlers) {
if (b != from) b.brokeredNodeSelect(nodes, selected);
}
}
}
SelectionBroker.prototype.clear = function(from) {
this.lastDispatchingHandler = null;
if (!this.dispatching) {
try {
this.dispatching = true;
this.brokers.forEach(function(b) {
if (b != from) {
b.brokeredClear();
}
});
} finally {
this.dispatching = false;
broadcastNodeSelect(from, nodes, selected) {
let broker = this;
for (var b of this.nodeHandlers) {
if (b != from) b.brokeredNodeSelect(nodes, selected);
}
const sourcePositions = this.sourceResolver.nodeIdsToSourcePositions(nodes);
for (var b of this.sourcePositionHandlers) {
if (b != from) b.brokeredSourcePositionSelect(sourcePositions, selected);
}
}
broadcastBlockSelect(from, blocks, selected) {
let broker = this;
for (var b of this.blockHandlers) {
if (b != from) b.brokeredBlockSelect(blocks, selected);
}
}
broadcastClear(from) {
this.sourcePositionHandlers.forEach(function (b) {
if (b != from) b.brokeredClear();
});
this.nodeHandlers.forEach(function (b) {
if (b != from) b.brokeredClear();
});
this.blockHandlers.forEach(function (b) {
if (b != from) b.brokeredClear();
});
}
}
......@@ -2,107 +2,59 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var Selection = function(handler) {
this.handler = handler;
this.selectionBase = null;
this.lastSelection = null;
this.selection = new Set();
}
Selection.prototype.isEmpty = function() {
return this.selection.size == 0;
}
Selection.prototype.clear = function() {
var handler = this.handler;
this.selectionBase = null;
this.lastSelection = null;
handler.select(this.selection, false);
handler.clear();
this.selection = new Set();
}
class Selection {
constructor(stringKeyFnc) {
this.selection = new Map();
this.stringKey = stringKeyFnc;
}
isEmpty() {
return this.selection.size == 0;
}
count = 0;
clear() {
this.selection = new Map();
}
Selection.prototype.select = function(s, isSelected) {
var handler = this.handler;
if (!(Symbol.iterator in Object(s))) { s = [s]; }
if (isSelected) {
let first = true;
for (let i of s) {
if (first) {
this.selectionBase = i;
this.lastSelection = i;
first = false;
select(s, isSelected) {
if (!isIterable(s)) { s = [s]; }
for (const i of s) {
if (!i) continue;
if (isSelected == undefined) {
isSelected = !this.selection.has(this.stringKey(i));
}
this.selection.add(i);
}
handler.select(this.selection, true);
} else {
let unselectSet = new Set();
for (let i of s) {
if (this.selection.has(i)) {
unselectSet.add(i);
this.selection.delete(i);
if (isSelected) {
this.selection.set(this.stringKey(i), i);
} else {
this.selection.delete(this.stringKey(i));
}
}
handler.select(unselectSet, false);
}
}
isSelected(i) {
return this.selection.has(this.stringKey(i));
}
Selection.prototype.extendTo = function(pos) {
if (pos == this.lastSelection || this.lastSelection === null) return;
isKeySelected(key) {
return this.selection.has(key);
}
var handler = this.handler;
var pos_diff = handler.selectionDifference(pos, true, this.lastSelection, false);
var unselect_diff = [];
if (pos_diff.length == 0) {
pos_diff = handler.selectionDifference(this.selectionBase, false, pos, true);
if (pos_diff.length != 0) {
unselect_diff = handler.selectionDifference(this.lastSelection, true, this.selectionBase, false);
this.selection = new Set();
this.selection.add(this.selectionBase);
for (var d of pos_diff) {
this.selection.add(d);
}
} else {
unselect_diff = handler.selectionDifference(this.lastSelection, true, pos, false);
for (var d of unselect_diff) {
this.selection.delete(d);
}
}
} else {
unselect_diff = handler.selectionDifference(this.selectionBase, false, this.lastSelection, true);
if (unselect_diff != 0) {
pos_diff = handler.selectionDifference(pos, true, this.selectionBase, false);
if (pos_diff.length == 0) {
unselect_diff = handler.selectionDifference(pos, false, this.lastSelection, true);
}
for (var d of unselect_diff) {
this.selection.delete(d);
}
}
if (pos_diff.length != 0) {
for (var d of pos_diff) {
this.selection.add(d);
}
selectedKeys() {
var result = new Set();
for (var i of this.selection.keys()) {
result.add(i);
}
return result;
}
handler.select(unselect_diff, false);
handler.select(pos_diff, true);
this.lastSelection = pos;
}
Selection.prototype.detachSelection = function() {
var result = new Set();
for (var i of this.selection) {
result.add(i);
detachSelection() {
var result = new Set();
for (var i of this.selection.keys()) {
result.add(i);
}
this.clear();
return result;
}
this.clear();
return result;
[Symbol.iterator]() { return this.selection.values() }
}
// Copyright 2018 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function sourcePositionLe(a, b) {
if (a.inliningId == b.inliningId) {
return a.scriptOffset - b.scriptOffset;
}
return a.inliningId - b.inliningId;
}
function sourcePositionEq(a, b) {
return a.inliningId == b.inliningId &&
a.scriptOffset == b.scriptOffset;
}
function sourcePositionToStringKey(sourcePosition) {
if (!sourcePosition) return "undefined";
return "" + sourcePosition.inliningId + ":" + sourcePosition.scriptOffset;
}
class SourceResolver {
constructor() {
// Maps node ids to source positions.
this.nodePositionMap = [];
// Maps source ids to source objects.
this.sources = [];
// Maps inlining ids to inlining objects.
this.inlinings = [];
// Maps source position keys to inlinings.
this.inliningsMap = new Map();
// Maps source position keys to node ids.
this.positionToNodes = new Map();
// Maps phase ids to phases.
this.phases = [];
// The disassembly phase is stored separately.
this.disassemblyPhase = undefined;
}
setSources(sources, mainBackup) {
if (sources) {
for (let [sourceId, source] of Object.entries(sources)) {
this.sources[sourceId] = source;
this.sources[sourceId].sourcePositions = [];
}
}
// This is a fallback if the JSON is incomplete (e.g. due to compiler crash).
if (!this.sources[-1]) {
this.sources[-1] = mainBackup;
this.sources[-1].sourcePositions = [];
}
}
setInlinings(inlinings) {
if (inlinings) {
for (const [inliningId, inlining] of Object.entries(inlinings)) {
this.inlinings[inliningId] = inlining;
this.inliningsMap.set(sourcePositionToStringKey(inlining.inliningPosition), inlining);
}
}
// This is a default entry for the script itself that helps
// keep other code more uniform.
this.inlinings[-1] = { sourceId: -1 };
}
setNodePositionMap(map) {
if (!map) return;
if (typeof map[0] != 'object') {
const alternativeMap = {};
for (const [nodeId, scriptOffset] of Object.entries(map)) {
alternativeMap[nodeId] = { scriptOffset: scriptOffset, inliningId: -1 };
}
map = alternativeMap;
};
for (const [nodeId, sourcePosition] of Object.entries(map)) {
if (sourcePosition == undefined) {
console.log("Warning: undefined source position ", sourcePosition, " for nodeId ", nodeId);
}
const inliningId = sourcePosition.inliningId;
const inlining = this.inlinings[inliningId];
if (inlining) {
const sourceId = inlining.sourceId;
this.sources[sourceId].sourcePositions.push(sourcePosition);
}
this.nodePositionMap[nodeId] = sourcePosition;
let key = sourcePositionToStringKey(sourcePosition);
if (!this.positionToNodes.has(key)) {
this.positionToNodes.set(key, []);
}
this.positionToNodes.get(key).push(nodeId);
}
for (const [sourceId, source] of Object.entries(this.sources)) {
source.sourcePositions = sortUnique(source.sourcePositions,
sourcePositionLe, sourcePositionEq);
}
}
sourcePositionsToNodeIds(sourcePositions) {
const nodeIds = new Set();
for (const sp of sourcePositions) {
let key = sourcePositionToStringKey(sp);
let nodeIdsForPosition = this.positionToNodes.get(key);
if (!nodeIdsForPosition) continue;
for (const nodeId of nodeIdsForPosition) {
nodeIds.add(nodeId);
}
}
return nodeIds;
}
nodeIdsToSourcePositions(nodeIds) {
const sourcePositions = new Map();
for (const nodeId of nodeIds) {
let sp = this.nodePositionMap[nodeId];
let key = sourcePositionToStringKey(sp);
sourcePositions.set(key, sp);
}
const sourcePositionArray = [];
for (const sp of sourcePositions.values()) {
sourcePositionArray.push(sp);
}
return sourcePositionArray;
}
forEachSource(f) {
this.sources.forEach(f);
}
translateToSourceId(sourceId, location) {
for (const position of this.getInlineStack(location)) {
let inlining = this.inlinings[position.inliningId];
if (!inlining) continue;
if (inlining.sourceId == sourceId) {
return position;
}
}
return location;
}
addInliningPositions(sourcePosition, locations) {
let inlining = this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
if (!inlining) return;
let sourceId = inlining.sourceId
const source = this.sources[sourceId];
for (const sp of source.sourcePositions) {
locations.push(sp);
this.addInliningPositions(sp, locations);
}
}
getInliningForPosition(sourcePosition) {
return this.inliningsMap.get(sourcePositionToStringKey(sourcePosition));
}
getSource(sourceId) {
return this.sources[sourceId];
}
getSourceName(sourceId) {
const source = this.sources[sourceId];
return `${source.sourceName}:${source.functionName}`;
}
sourcePositionFor(sourceId, scriptOffset) {
if (!this.sources[sourceId]) {
return null;
}
const list = this.sources[sourceId].sourcePositions;
for (let i = 0; i < list.length; i++) {
const sourcePosition = list[i]
const position = sourcePosition.scriptOffset;
const nextPosition = list[Math.min(i + 1, list.length - 1)].scriptOffset;
if ((position <= scriptOffset && scriptOffset < nextPosition)) {
return sourcePosition;
}
}
return null;
}
sourcePositionsInRange(sourceId, start, end) {
if (!this.sources[sourceId]) return [];
const res = [];
const list = this.sources[sourceId].sourcePositions;
for (let i = 0; i < list.length; i++) {
const sourcePosition = list[i]
if (start <= sourcePosition.scriptOffset && sourcePosition.scriptOffset < end) {
res.push(sourcePosition);
}
}
return res;
}
getInlineStack(sourcePosition) {
if (!sourcePosition) {
return [];
}
let inliningStack = [];
let cur = sourcePosition;
while (cur && cur.inliningId != -1) {
inliningStack.push(cur);
let inlining = this.inlinings[cur.inliningId];
if (!inlining) {
break;
}
cur = inlining.inliningPosition;
}
if (cur && cur.inliningId == -1) {
inliningStack.push(cur);
}
return inliningStack;
}
parsePhases(phases) {
for (const [phaseId, phase] of Object.entries(phases)) {
if (phase.type == 'disassembly') {
this.disassemblyPhase = phase;
} else if (phase.type == 'schedule') {
this.phases.push(this.parseSchedule(phase))
} else {
this.phases.push(phase);
}
}
}
repairPhaseId(anyPhaseId) {
return Math.max(0, Math.min(anyPhaseId, this.phases.length - 1))
}
getPhase(phaseId) {
return this.phases[phaseId];
}
forEachPhase(f) {
this.phases.forEach(f);
}
parseSchedule(phase) {
function createNode(state, match) {
let inputs = [];
if (match.groups.args) {
const nodeIdsString = match.groups.args.replace(/\s/g, '');
const nodeIdStrings = nodeIdsString.split(',');
inputs = nodeIdStrings.map((n) => Number.parseInt(n, 10));
}
const node = {id: Number.parseInt(match.groups.id, 10),
label: match.groups.label,
inputs: inputs};
if (match.groups.blocks) {
const nodeIdsString = match.groups.blocks.replace(/\s/g, '').replace(/B/g,'');
const nodeIdStrings = nodeIdsString.split(',');
const successors = nodeIdStrings.map((n) => Number.parseInt(n, 10));
state.currentBlock.succ = successors;
}
state.nodes[node.id] = node;
state.currentBlock.nodes.push(node);
}
function createBlock(state, match) {
let predecessors = [];
if (match.groups.in) {
const blockIdsString = match.groups.in.replace(/\s/g, '').replace(/B/g, '');
const blockIdStrings = blockIdsString.split(',');
predecessors = blockIdStrings.map((n) => Number.parseInt(n, 10));
}
const block = {id: Number.parseInt(match.groups.id, 10),
isDeferred: match.groups.deferred != undefined,
pred: predecessors.sort(),
succ: [],
nodes: []};
state.blocks[block.id] = block;
state.currentBlock = block;
}
function setGotoSuccessor(state, match) {
state.currentBlock.succ = [Number.parseInt(match.groups.successor.replace(/\s/g, ''), 10)];
}
const rules = [
{
lineRegexps:
[ /^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)$/,
/^\s*(?<id>\d+):\ (?<label>.*)\((?<args>.*)\)\ ->\ (?<blocks>.*)$/,
/^\s*(?<id>\d+):\ (?<label>.*)$/
],
process: createNode
},
{
lineRegexps:
[/^\s*---\s*BLOCK\ B(?<id>\d+)\s*(?<deferred>\(deferred\))?(\ <-\ )?(?<in>[^-]*)?\ ---$/
],
process: createBlock
},
{
lineRegexps:
[/^\s*Goto\s*->\s*B(?<successor>\d+)\s*$/
],
process: setGotoSuccessor
}
];
const lines = phase.data.split(/[\n]/);
const state = { currentBlock: undefined, blocks: [], nodes: [] };
nextLine:
for (const line of lines) {
for (const rule of rules) {
for (const lineRegexp of rule.lineRegexps) {
const match = line.match(lineRegexp);
if (match) {
rule.process(state, match);
continue nextLine;
}
}
}
console.log("Warning: unmatched schedule line \"" + line + "\"");
}
phase.schedule = state;
return phase;
}
}
......@@ -4,43 +4,174 @@
"use strict";
function anyToString(x) {
return "" + x;
}
class TextView extends View {
constructor(id, broker, patterns, allowSpanSelection) {
constructor(id, broker, patterns) {
super(id, broker);
let view = this;
view.hide();
view.textListNode = view.divNode.getElementsByTagName('ul')[0];
view.fillerSvgElement = view.divElement.append("svg").attr('version','1.1').attr("width", "0");
view.patterns = patterns;
view.allowSpanSelection = allowSpanSelection;
view.nodeToLineMap = [];
var selectionHandler = {
clear: function() {
broker.clear(selectionHandler);
},
select: function(items, selected) {
for (let i of items) {
if (selected) {
i.classList.add("selected");
} else {
i.classList.remove("selected");
}
}
broker.clear(selectionHandler);
broker.select(selectionHandler, view.getLocations(items), selected);
view.nodeIdToHtmlElementsMap = new Map();
view.blockIdToHtmlElementsMap = new Map();
view.sourcePositionToHtmlElementsMap = new Map();
view.blockIdtoNodeIds = new Map();
view.nodeIdToBlockId = [];
view.selection = new Selection(anyToString);
view.blockSelection = new Selection(anyToString);
view.sourcePositionSelection = new Selection(sourcePositionToStringKey);
const selectionHandler = {
clear: function () {
view.selection.clear();
view.updateSelection();
broker.broadcastClear(selectionHandler);
},
selectionDifference: function(span1, inclusive1, span2, inclusive2) {
return null;
select: function (nodeIds, selected) {
view.selection.select(nodeIds, selected);
const blockIds = view.blockIdsForNodeIds(nodeIds);
view.blockSelection.select(blockIds, selected);
view.updateSelection();
broker.broadcastNodeSelect(selectionHandler, view.selection.selectedKeys(), selected);
broker.broadcastBlockSelect(view.blockSelectionHandler, blockIds, selected);
},
brokeredSelect: function(locations, selected) {
view.selectLocations(locations, selected, true);
brokeredNodeSelect: function (nodeIds, selected) {
const firstSelect = view.blockSelection.isEmpty();
view.selection.select(nodeIds, selected);
const blockIds = view.blockIdsForNodeIds(nodeIds);
view.blockSelection.select(blockIds, selected);
view.updateSelection(firstSelect);
},
brokeredClear: function() {
brokeredClear: function () {
view.selection.clear();
view.updateSelection();
}
};
view.selection = new Selection(selectionHandler);
broker.addSelectionHandler(selectionHandler);
this.selectionHandler = selectionHandler;
broker.addNodeHandler(selectionHandler);
view.divNode.onmouseup = function (e) {
if (!e.shiftKey) {
view.selectionHandler.clear();
}
}
const blockSelectionHandler = {
clear: function () {
view.blockSelection.clear();
view.updateSelection();
broker.broadcastClear(blockSelectionHandler);
},
select: function (blockIds, selected) {
view.blockSelection.select(blockIds, selected);
view.updateSelection();
broker.broadcastBlockSelect(blockSelectionHandler, blockIds, selected);
},
brokeredBlockSelect: function (blockIds, selected) {
const firstSelect = view.blockSelection.isEmpty();
view.blockSelection.select(blockIds, selected);
view.updateSelection(firstSelect);
},
brokeredClear: function () {
view.blockSelection.clear();
view.updateSelection();
}
};
this.blockSelectionHandler = blockSelectionHandler;
broker.addBlockHandler(blockSelectionHandler);
const sourcePositionSelectionHandler = {
clear: function () {
view.sourcePositionSelection.clear();
view.updateSelection();
broker.broadcastClear(sourcePositionSelectionHandler);
},
select: function (sourcePositions, selected) {
view.sourcePositionSelection.select(sourcePositions, selected);
view.updateSelection();
broker.broadcastSourcePositionSelect(sourcePositionSelectionHandler, sourcePositions, selected);
},
brokeredSourcePositionSelect: function (sourcePositions, selected) {
const firstSelect = view.sourcePositionSelection.isEmpty();
view.sourcePositionSelection.select(sourcePositions, selected);
view.updateSelection(firstSelect);
},
brokeredClear: function () {
view.sourcePositionSelection.clear();
view.updateSelection();
}
};
view.sourcePositionSelectionHandler = sourcePositionSelectionHandler;
broker.addSourcePositionHandler(sourcePositionSelectionHandler);
}
addHtmlElementForNodeId(anyNodeId, htmlElement) {
const nodeId = anyToString(anyNodeId);
if (!this.nodeIdToHtmlElementsMap.has(nodeId)) {
this.nodeIdToHtmlElementsMap.set(nodeId, []);
}
this.nodeIdToHtmlElementsMap.get(nodeId).push(htmlElement);
}
addHtmlElementForSourcePosition(sourcePosition, htmlElement) {
const key = sourcePositionToStringKey(sourcePosition);
if (!this.sourcePositionToHtmlElementsMap.has(key)) {
this.sourcePositionToHtmlElementsMap.set(key, []);
}
this.sourcePositionToHtmlElementsMap.get(key).push(htmlElement);
}
addHtmlElementForBlockId(anyBlockId, htmlElement) {
const blockId = anyToString(anyBlockId);
if (!this.blockIdToHtmlElementsMap.has(blockId)) {
this.blockIdToHtmlElementsMap.set(blockId, []);
}
this.blockIdToHtmlElementsMap.get(blockId).push(htmlElement);
}
addNodeIdToBlockId(anyNodeId, anyBlockId) {
const blockId = anyToString(anyBlockId);
if (!this.blockIdtoNodeIds.has(blockId)) {
this.blockIdtoNodeIds.set(blockId, []);
}
this.blockIdtoNodeIds.get(blockId).push(anyToString(anyNodeId));
this.nodeIdToBlockId[anyNodeId] = blockId;
}
blockIdsForNodeIds(nodeIds) {
const blockIds = [];
for (const nodeId of nodeIds) {
const blockId = this.nodeIdToBlockId[nodeId];
if (blockId == undefined) continue;
blockIds.push(blockId);
}
return blockIds;
}
updateSelection(scrollIntoView) {
if (this.divNode.parentNode == null) return;
const mkVisible = new ViewElements(this.divNode.parentNode);
const view = this;
for (const [nodeId, elements] of this.nodeIdToHtmlElementsMap.entries()) {
const isSelected = view.selection.isSelected(nodeId);
for (const element of elements) {
mkVisible.consider(element, isSelected);
element.classList.toggle("selected", isSelected);
}
}
for (const [blockId, elements] of this.blockIdToHtmlElementsMap.entries()) {
const isSelected = view.blockSelection.isSelected(blockId);
for (const element of elements) {
mkVisible.consider(element, isSelected);
element.classList.toggle("selected", isSelected);
}
}
for (const [sourcePositionKey, elements] of this.sourcePositionToHtmlElementsMap.entries()) {
const isSelected = view.sourcePositionSelection.isKeySelected(sourcePositionKey);
for (const element of elements) {
mkVisible.consider(element, isSelected);
element.classList.toggle("selected", isSelected);
}
}
mkVisible.apply(scrollIntoView);
}
setPatterns(patterns) {
......@@ -55,77 +186,68 @@ class TextView extends View {
}
}
sameLocation(l1, l2) {
createFragment(text, style) {
let view = this;
if (l1.block_id != undefined && l2.block_id != undefined &&
l1.block_id == l2.block_id && l1.node_id === undefined) {
return true;
}
let fragment = document.createElement("SPAN");
if (l1.address != undefined && l1.address == l2.address) {
return true;
if (style.blockId != undefined) {
const blockId = style.blockId(text);
if (blockId != undefined) {
fragment.blockId = blockId;
this.addHtmlElementForBlockId(blockId, fragment);
}
}
let node1 = l1.node_id;
let node2 = l2.node_id;
if (typeof style.link == 'function') {
fragment.classList.add('linkable-text');
fragment.onmouseup = function (e) {
e.stopPropagation();
style.link(text)
};
}
if (node1 === undefined || node2 == undefined) {
if (l1.pos_start === undefined || l2.pos_start == undefined) {
return false;
}
if (l1.pos_start == -1 || l2.pos_start == -1) {
return false;
}
if (l1.pos_start < l2.pos_start) {
return l1.pos_end > l2.pos_start;
} {
return l1.pos_start < l2.pos_end;
if (typeof style.nodeId == 'function') {
const nodeId = style.nodeId(text);
if (nodeId != undefined) {
fragment.nodeId = nodeId;
this.addHtmlElementForNodeId(nodeId, fragment);
}
}
return l1.node_id == l2.node_id;
}
selectLocations(locations, selected, makeVisible) {
let view = this;
let s = new Set();
for (let l of locations) {
for (let i = 0; i < view.textListNode.children.length; ++i) {
let child = view.textListNode.children[i];
if (child.location != undefined && view.sameLocation(l, child.location)) {
s.add(child);
}
if (typeof style.sourcePosition === 'function') {
const sourcePosition = style.sourcePosition(text);
if (sourcePosition != undefined) {
fragment.sourcePosition = sourcePosition;
//this.addHtmlElementForNodeId(nodeId, fragment);
}
}
view.selectCommon(s, selected, makeVisible);
}
getLocations(items) {
let result = [];
let lastObject = null;
for (let i of items) {
if (i.location) {
result.push(i.location);
}
if (typeof style.assignSourcePosition === 'function') {
fragment.sourcePosition = style.assignSourcePosition();
this.addHtmlElementForSourcePosition(fragment.sourcePosition, fragment)
}
return result;
}
createFragment(text, style) {
let view = this;
let span = document.createElement("SPAN");
span.onmousedown = function(e) {
view.mouseDownSpan(span, e);
if (typeof style.assignBlockId === 'function') {
fragment.blockId = style.assignBlockId();
this.addNodeIdToBlockId(fragment.nodeId, fragment.blockId);
}
if (style != undefined) {
span.classList.add(style);
if (typeof style.linkHandler == 'function') {
const handler = style.linkHandler(text, fragment)
if (handler !== undefined) {
fragment.classList.add('linkable-text');
fragment.onmouseup = handler;
}
}
span.innerHTML = text;
return span;
}
appendFragment(li, fragment) {
li.appendChild(fragment);
if (style.css != undefined) {
const css = isIterable(style.css) ? style.css : [style.css];
for (const cls of css) {
fragment.classList.add(cls);
}
}
fragment.innerHTML = text;
return fragment;
}
processLine(line) {
......@@ -141,18 +263,8 @@ class TextView extends View {
let style = pattern[1] != null ? pattern[1] : {};
let text = matches[0];
if (text != '') {
let fragment = view.createFragment(matches[0], style.css);
if (style.link) {
fragment.classList.add('linkable-text');
fragment.link = style.link;
}
let fragment = view.createFragment(matches[0], style);
result.push(fragment);
if (style.location != undefined) {
let location = style.location(text);
if (location != undefined) {
fragment.location = location;
}
}
}
line = line.substr(matches[0].length);
}
......@@ -162,7 +274,7 @@ class TextView extends View {
}
if (line == "") {
if (nextPatternSet != -1) {
throw("illegal parsing state in text-view in patternSet" + patternSet);
throw ("illegal parsing state in text-view in patternSet" + patternSet);
}
return result;
}
......@@ -171,90 +283,22 @@ class TextView extends View {
}
}
if (beforeLine == line) {
throw("input not consumed in text-view in patternSet" + patternSet);
throw ("input not consumed in text-view in patternSet" + patternSet);
}
}
}
select(s, selected, makeVisible) {
let view = this;
view.selection.clear();
view.selectCommon(s, selected, makeVisible);
}
selectCommon(s, selected, makeVisible) {
let view = this;
let firstSelect = makeVisible && view.selection.isEmpty();
if ((typeof s) === 'function') {
for (let i = 0; i < view.textListNode.children.length; ++i) {
let child = view.textListNode.children[i];
if (child.location && s(child.location)) {
if (firstSelect) {
makeContainerPosVisible(view.parentNode, child.offsetTop);
firstSelect = false;
}
view.selection.select(child, selected);
}
}
} else if (typeof s[Symbol.iterator] === 'function') {
if (firstSelect) {
for (let i of s) {
makeContainerPosVisible(view.parentNode, i.offsetTop);
break;
}
}
view.selection.select(s, selected);
} else {
if (firstSelect) {
makeContainerPosVisible(view.parentNode, s.offsetTop);
}
view.selection.select(s, selected);
}
}
mouseDownLine(li, e) {
let view = this;
e.stopPropagation();
if (!e.shiftKey) {
view.selection.clear();
}
if (li.location != undefined) {
view.selectLocations([li.location], true, false);
}
}
mouseDownSpan(span, e) {
let view = this;
if (view.allowSpanSelection) {
e.stopPropagation();
if (!e.shiftKey) {
view.selection.clear();
}
select(li, true);
} else if (span.link) {
span.link(span.textContent);
e.stopPropagation();
}
}
processText(text) {
let view = this;
let textLines = text.split(/[\n]/);
let lineNo = 0;
for (let line of textLines) {
let li = document.createElement("LI");
li.onmousedown = function(e) {
view.mouseDownLine(li, e);
}
li.className = "nolinenums";
li.lineNo = lineNo++;
let fragments = view.processLine(line);
for (let fragment of fragments) {
view.appendFragment(li, fragment);
}
let lineLocation = view.lineLocation(li);
if (lineLocation != undefined) {
li.location = lineLocation;
li.appendChild(fragment);
}
view.textListNode.appendChild(li);
}
......@@ -262,15 +306,8 @@ class TextView extends View {
initializeContent(data, rememberedSelection) {
let view = this;
view.selection.clear();
view.clearText();
view.processText(data);
var fillerSize = document.documentElement.clientHeight -
view.textListNode.clientHeight;
if (fillerSize < 0) {
fillerSize = 0;
}
view.fillerSvgElement.attr("height", fillerSize);
}
deleteContent() {
......@@ -279,18 +316,4 @@ class TextView extends View {
isScrollable() {
return true;
}
detachSelection() {
return null;
}
lineLocation(li) {
let view = this;
for (let i = 0; i < li.children.length; ++i) {
let fragment = li.children[i];
if (fragment.location != undefined && !view.allowSpanSelection) {
return fragment.location;
}
}
}
}
......@@ -50,24 +50,73 @@
background-color: #FFFF33;
}
.selected.block,
.selected.block-id,
.selected.schedule-block {
background-color: #AAFFAA;
}
ol.linenums {
-webkit-padding-start: 8px;
}
.line-number {
display:inline-block;
min-width: 3ex;
text-align: right;
color: gray;
padding-right:1ex;
font-size: 10px;
user-select: none;
}
.line-number:hover {
background-color: #CCCCCC;
}
.prettyprint ol.linenums > li {
list-style-type: decimal;
!important
padding-top: 3px;
display: block;
}
.source-container {
border-bottom: 2px solid #AAAAAA;
}
.code-header {
background-color: #CCCCCC;
padding-left: 1em;
padding-right: 1em;
padding-top: 1ex;
padding-bottom: 1ex;
font-family: monospace;
user-select: none;
}
.main-source .code-header {
border-top: 2px solid #AAAAAA;
font-weight: bold;
}
.code-header .code-file-function {
font-family: monospace;
float: left;
user-select: text;
}
.code-header .code-mode {
float: right;
font-family: sans-serif;
font-size: small;
}
body {
html, body {
margin: 0;
padding: 0;
height: 100vh;
width: 100vw;
overflow:hidden;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
/*height: 99vh;
width: 99vw;*/
overflow: hidden;
}
p {
......@@ -95,7 +144,7 @@ g.unsorted rect {
}
div.scrollable {
overflow-y: _croll; overflow-x: hidden;
overflow-y: auto; overflow-x: hidden;
}
g.turbonode[relToHover="input"] rect {
......@@ -243,10 +292,13 @@ span.linkable-text:hover {
#left {
float: left;
user-select: none;
}
#middle {
float:left; background-color: #F8F8F8;
float:left;
background-color: #F8F8F8;
user-select: none;
}
#right {
......@@ -267,6 +319,11 @@ span.linkable-text:hover {
left: 0;
}
#graph {
width: 100%;
height: 100%;
}
#graph-toolbox-anchor {
height: 0px;
}
......@@ -308,7 +365,7 @@ span.linkable-text:hover {
padding: 0.5em;
}
#hidden-file-upload {
#upload-helper {
display: none;
}
......@@ -363,4 +420,165 @@ text {
.resizer-right.dragged {
background: orange;
}
.source-position {
/* border-left: 1px solid #FF3333; */
width: 0;
display: inline-block;
}
.source-position .inlining-marker {
content: "";
position: relative;
display: inline-block;
top: -0.5ex;
margin-left: -4px;
margin-right: -4px;
border-width: 5px;
border-style: solid;
border-color: #555 transparent transparent transparent;
}
.source-position .marker {
content: "";
display: inline-block;
position: relative;
bottom: -1ex;
width: 0px;
margin-left: -4px;
margin-right: -4px;
border-width: 5px;
border-style: solid;
border-color: transparent transparent #555 transparent;
}
.source-position.selected .marker {
border-color: transparent transparent #F00 transparent;
}
.source-position .inlining-marker:hover {
border-color: transparent transparent #AA5 transparent;
}
.source-position .inlining-marker[data-descr]:hover::after {
content: attr(data-descr);
position: absolute;
font-size: 10px;
z-index: 1;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 6px;
top: 6px;
left: 50%;
margin-left: -80px;
}
#schedule {
font-family: monospace;
}
.schedule-block {
margin: 5px;
background-color: white;
}
.schedule-block .block-id {
display: inline-block;
font-size:large;
text-decoration: underline;
}
.schedule-block .block-id:hover {
font-weight: bold;
}
.schedule-block > .block-id::before {
content: "Block B";
}
.schedule-block .block-list {
display: inline-block;
}
.schedule-block .block-list * {
display: inline-block;
}
.schedule-block .block-list .block-id {
padding-left: 1ex;
}
.schedule-block .block-list .block-id:before {
content: "B";
}
.schedule-block .predecessor-list::before {
display: inline-block;
content: " \2B05 ";
padding-left: 1ex;
padding-right: 1ex;
}
.schedule-block .successor-list::before {
display: inline-block;
content: " \2B95 ";
padding-left: 1ex;
padding-right: 1ex;
}
.schedule-block .nodes {
padding-left: 5px;
}
.schedule-block .nodes .node * {
display:inline-block;
}
.schedule-block .nodes .node .node-id {
padding-right: 1ex;
min-width: 5ex;
text-align: right;
}
.schedule-block .nodes .node .node-id:after {
content: ":";
}
.schedule-block .nodes .node .node-label {
user-select: text;
}
.schedule-block .nodes .node .parameter-list:before {
content: "(";
}
.schedule-block .nodes .node .parameter-list:after {
content: ")";
}
.clickable:hover {
text-decoration: underline;
}
.clickable:hover {
font-weight: bold;
}
.comma-sep-list > * {
padding-right: 1ex;
}
.comma-sep-list > *:after {
content: ",";
}
.comma-sep-list > *:last-child:after {
content: "";
}
.comma-sep-list > *:last-child {
padding-right: 0ex;
}
\ No newline at end of file
......@@ -12,22 +12,22 @@ class Snapper {
snapper.disassemblyExpand = d3.select("#" + DISASSEMBLY_EXPAND_ID);
snapper.disassemblyCollapse = d3.select("#" + DISASSEMBLY_COLLAPSE_ID);
d3.select("#source-collapse").on("click", function(){
d3.select("#source-collapse").on("click", function () {
resizer.snapper.toggleSourceExpanded();
});
d3.select("#disassembly-collapse").on("click", function(){
d3.select("#disassembly-collapse").on("click", function () {
resizer.snapper.toggleDisassemblyExpanded();
});
}
getLastExpandedState(type, default_state) {
var state = window.sessionStorage.getItem("expandedState-"+type);
var state = window.sessionStorage.getItem("expandedState-" + type);
if (state === null) return default_state;
return state === 'true';
}
setLastExpandedState(type, state) {
window.sessionStorage.setItem("expandedState-"+type, state);
window.sessionStorage.setItem("expandedState-" + type, state);
}
toggleSourceExpanded() {
......@@ -97,51 +97,50 @@ class Resizer {
resizer.right = d3.select("#" + GENERATED_PANE_ID);
resizer.resizer_left = d3.select('.resizer-left');
resizer.resizer_right = d3.select('.resizer-right');
resizer.sep_left = resizer.client_width/3;
resizer.sep_right = resizer.client_width/3*2;
resizer.sep_left = resizer.client_width / 3;
resizer.sep_right = resizer.client_width / 3 * 2;
resizer.sep_left_snap = 0;
resizer.sep_right_snap = 0;
// Offset to prevent resizers from sliding slightly over one another.
resizer.sep_width_offset = 7;
let dragResizeLeft = d3.behavior.drag()
.on('drag', function() {
.on('drag', function () {
let x = d3.mouse(this.parentElement)[0];
resizer.sep_left = Math.min(Math.max(0,x), resizer.sep_right-resizer.sep_width_offset);
resizer.sep_left = Math.min(Math.max(0, x), resizer.sep_right - resizer.sep_width_offset);
resizer.updatePanes();
})
.on('dragstart', function() {
.on('dragstart', function () {
resizer.resizer_left.classed("dragged", true);
let x = d3.mouse(this.parentElement)[0];
if (x > dead_width) {
resizer.sep_left_snap = resizer.sep_left;
}
})
.on('dragend', function() {
.on('dragend', function () {
resizer.resizer_left.classed("dragged", false);
});
resizer.resizer_left.call(dragResizeLeft);
let dragResizeRight = d3.behavior.drag()
.on('drag', function() {
.on('drag', function () {
let x = d3.mouse(this.parentElement)[0];
resizer.sep_right = Math.max(resizer.sep_left+resizer.sep_width_offset, Math.min(x, resizer.client_width));
resizer.sep_right = Math.max(resizer.sep_left + resizer.sep_width_offset, Math.min(x, resizer.client_width));
resizer.updatePanes();
})
.on('dragstart', function() {
.on('dragstart', function () {
resizer.resizer_right.classed("dragged", true);
let x = d3.mouse(this.parentElement)[0];
if (x < (resizer.client_width-dead_width)) {
if (x < (resizer.client_width - dead_width)) {
resizer.sep_right_snap = resizer.sep_right;
}
})
.on('dragend', function() {
.on('dragend', function () {
resizer.resizer_right.classed("dragged", false);
});;
resizer.resizer_right.call(dragResizeRight);
window.onresize = function(){
window.onresize = function () {
resizer.updateWidths();
/*fitPanesToParents();*/
resizer.updatePanes();
};
}
......@@ -152,7 +151,7 @@ class Resizer {
this.resizer_left.classed("snapped", left_snapped);
this.resizer_right.classed("snapped", right_snapped);
this.left.style('width', this.sep_left + 'px');
this.middle.style('width', (this.sep_right-this.sep_left) + 'px');
this.middle.style('width', (this.sep_right - this.sep_left) + 'px');
this.right.style('width', (this.client_width - this.sep_right) + 'px');
this.resizer_left.style('left', this.sep_left + 'px');
this.resizer_right.style('right', (this.client_width - this.sep_right - 1) + 'px');
......@@ -168,21 +167,20 @@ class Resizer {
}
}
document.onload = (function(d3){
document.onload = (function (d3) {
"use strict";
var jsonObj;
var svg = null;
var svg = null;
var graph = null;
var schedule = null;
var empty = null;
var currentPhaseView = null;
var disassemblyView = null;
var sourceView = null;
var sourceViews = [];
var selectionBroker = null;
var sourceResolver = null;
let resizer = new Resizer(panesUpdatedCallback, 100);
function panesUpdatedCallback() {
graph.fitGraphViewToWindow();
if (graph) graph.fitGraphViewToWindow();
}
function hideCurrentPhase() {
......@@ -206,115 +204,126 @@ document.onload = (function(d3){
if (phase.type == 'graph') {
displayPhaseView(graph, phase.data);
} else if (phase.type == 'schedule') {
displayPhaseView(schedule, phase.data);
} else {
displayPhaseView(empty, null);
displayPhaseView(schedule, phase);
}
}
function fitPanesToParents() {
d3.select("#left").classed("scrollable", false)
d3.select("#right").classed("scrollable", false);
function loadFile(txtRes) {
// If the JSON isn't properly terminated, assume compiler crashed and
// add best-guess empty termination
if (txtRes[txtRes.length - 2] == ',') {
txtRes += '{"name":"disassembly","type":"disassembly","data":""}]}';
}
try {
sourceViews.forEach((sv) => sv.hide());
hideCurrentPhase();
graph = null;
if (disassemblyView) disassemblyView.hide();
sourceViews = [];
sourceResolver = new SourceResolver();
selectionBroker = new SelectionBroker(sourceResolver);
const jsonObj = JSON.parse(txtRes);
let fnc = jsonObj.function;
// Backwards compatibility.
if (typeof fnc == 'string') {
fnc = {
functionName: fnc,
sourceId: -1,
startPosition: jsonObj.sourcePosition,
endPosition: jsonObj.sourcePosition + jsonObj.source.length,
sourceText: jsonObj.source
};
}
graph.fitGraphViewToWindow();
sourceResolver.setInlinings(jsonObj.inlinings);
sourceResolver.setSources(jsonObj.sources, fnc)
sourceResolver.setNodePositionMap(jsonObj.nodePositions);
sourceResolver.parsePhases(jsonObj.phases);
d3.select("#left").classed("scrollable", true);
d3.select("#right").classed("scrollable", true);
}
let sourceView = new CodeView(SOURCE_PANE_ID, selectionBroker, sourceResolver, fnc, CodeView.MAIN_SOURCE);
sourceView.show(null, null);
sourceViews.push(sourceView);
selectionBroker = new SelectionBroker();
sourceResolver.forEachSource((source) => {
let sourceView = new CodeView(SOURCE_PANE_ID, selectionBroker, sourceResolver, source, CodeView.INLINED_SOURCE);
sourceView.show(null, null);
sourceViews.push(sourceView);
});
disassemblyView = new DisassemblyView(GENERATED_PANE_ID, selectionBroker);
disassemblyView.initializeCode(fnc.sourceText);
if (sourceResolver.disassemblyPhase) {
disassemblyView.initializePerfProfile(jsonObj.eventCounts);
disassemblyView.show(sourceResolver.disassemblyPhase.data, null);
}
var selectMenu = document.getElementById('display-selector');
selectMenu.innerHTML = '';
sourceResolver.forEachPhase((phase) => {
var optionElement = document.createElement("option");
optionElement.text = phase.name;
selectMenu.add(optionElement, null);
});
selectMenu.onchange = function (item) {
window.sessionStorage.setItem("lastSelectedPhase", selectMenu.selectedIndex);
displayPhase(sourceResolver.getPhase(selectMenu.selectedIndex));
}
function initializeHandlers(g) {
d3.select("#hidden-file-upload").on("change", function() {
const initialPhaseIndex = sourceResolver.repairPhaseId(+window.sessionStorage.getItem("lastSelectedPhase"));
selectMenu.selectedIndex = initialPhaseIndex;
graph = new GraphView(d3, INTERMEDIATE_PANE_ID, selectionBroker);
schedule = new ScheduleView(INTERMEDIATE_PANE_ID, selectionBroker);
displayPhase(sourceResolver.getPhase(initialPhaseIndex));
d3.select("#search-input").attr("value", window.sessionStorage.getItem("lastSearch") || "");
} catch (err) {
if (window.confirm("Error: Exception during load of TurboFan JSON file:\n" +
"error: " + err.message + "\nDo you want to clear session storage?")) {
window.sessionStorage.clear();
}
return;
}
}
function initializeUploadHandlers() {
// The <input> form #upload-helper with type file can't be a picture.
// We hence keep it hidden, and forward the click from the picture
// button #upload.
d3.select("#upload").on("click",
() => document.getElementById("upload-helper").click());
d3.select("#upload-helper").on("change", function () {
if (window.File && window.FileReader && window.FileList) {
var uploadFile = this.files[0];
var uploadFile = this.files && this.files[0];
var filereader = new window.FileReader();
var consts = Node.consts;
filereader.onload = function(){
var txtRes = filereader.result;
// If the JSON isn't properly terminated, assume compiler crashed and
// add best-guess empty termination
if (txtRes[txtRes.length-2] == ',') {
txtRes += '{"name":"disassembly","type":"disassembly","data":""}]}';
}
try{
jsonObj = JSON.parse(txtRes);
hideCurrentPhase();
selectionBroker.setNodePositionMap(jsonObj.nodePositions);
sourceView.initializeCode(jsonObj.source, jsonObj.sourcePosition);
disassemblyView.initializeCode(jsonObj.source);
var selectMenu = document.getElementById('display-selector');
var disassemblyPhase = null;
selectMenu.innerHTML = '';
for (var i = 0; i < jsonObj.phases.length; ++i) {
var optionElement = document.createElement("option");
optionElement.text = jsonObj.phases[i].name;
if (optionElement.text == 'disassembly') {
disassemblyPhase = jsonObj.phases[i];
} else {
selectMenu.add(optionElement, null);
}
}
disassemblyView.initializePerfProfile(jsonObj.eventCounts);
disassemblyView.show(disassemblyPhase.data, null);
var initialPhaseIndex = +window.sessionStorage.getItem("lastSelectedPhase");
if (!(initialPhaseIndex in jsonObj.phases)) {
initialPhaseIndex = 0;
}
// We wish to show the remembered phase {lastSelectedPhase}, but
// this will crash if the first view we switch to is a
// ScheduleView. So we first switch to the first phase, which
// should never be a ScheduleView.
displayPhase(jsonObj.phases[0]);
displayPhase(jsonObj.phases[initialPhaseIndex]);
selectMenu.selectedIndex = initialPhaseIndex;
selectMenu.onchange = function(item) {
window.sessionStorage.setItem("lastSelectedPhase", selectMenu.selectedIndex);
displayPhase(jsonObj.phases[selectMenu.selectedIndex]);
}
fitPanesToParents();
d3.select("#search-input").attr("value", window.sessionStorage.getItem("lastSearch") || "");
}
catch(err) {
window.console.log("caught exception, clearing session storage just in case");
window.sessionStorage.clear(); // just in case
window.console.log("showing error");
window.alert("Invalid TurboFan JSON file\n" +
"error: " + err.message);
return;
}
filereader.onload = function (e) {
var txtRes = e.target.result;
loadFile(txtRes);
};
filereader.readAsText(uploadFile);
if (uploadFile)
filereader.readAsText(uploadFile);
} else {
alert("Can't load graph");
}
});
}
sourceView = new CodeView(SOURCE_PANE_ID, PR, "", 0, selectionBroker);
disassemblyView = new DisassemblyView(DISASSEMBLY_PANE_ID, selectionBroker);
graph = new GraphView(d3, GRAPH_PANE_ID, [], [], selectionBroker);
schedule = new ScheduleView(SCHEDULE_PANE_ID, selectionBroker);
empty = new EmptyView(EMPTY_PANE_ID, selectionBroker);
initializeUploadHandlers();
function handleSearch(e) {
if (currentPhaseView) {
currentPhaseView.searchInputAction(currentPhaseView, this)
}
}
initializeHandlers(graph);
d3.select("#search-input").on("keyup", handleSearch);
resizer.snapper.setSourceExpanded(resizer.snapper.getLastExpandedState("source", true));
resizer.snapper.setDisassemblyExpanded(resizer.snapper.getLastExpandedState("disassembly", false));
displayPhaseView(empty, null);
fitPanesToParents();
resizer.updatePanes();
})(window.d3);
This diff was suppressed by a .gitattributes entry.
......@@ -4,19 +4,41 @@
"use strict";
function makeContainerPosVisible(container, pos) {
var height = container.offsetHeight;
var margin = Math.floor(height / 4);
if (pos < container.scrollTop + margin) {
pos -= margin;
if (pos < 0) pos = 0;
container.scrollTop = pos;
return;
function computeScrollTop(container, element) {
const height = container.offsetHeight;
const margin = Math.floor(height / 4);
const pos = element.offsetTop;
const currentScrollTop = container.scrollTop;
if (pos < currentScrollTop + margin) {
return Math.max(0, pos - margin);
} else if (pos > (currentScrollTop + 3 * margin)) {
return Math.max(0, pos - 3 * margin);
}
if (pos > (container.scrollTop + 3 * margin)) {
pos = pos - 3 * margin;
if (pos < 0) pos = 0;
container.scrollTop = pos;
return pos;
}
class ViewElements {
constructor(container) {
this.container = container;
this.scrollTop = undefined;
}
consider(element, doConsider) {
if (!doConsider) return;
const newScrollTop = computeScrollTop(this.container, element);
if (isNaN(newScrollTop)) {
console.log("NOO")
}
if (this.scrollTop === undefined) {
this.scrollTop = newScrollTop;
} else {
this.scrollTop = Math.min(this.scrollTop, newScrollTop);
}
}
apply(doApply) {
if (!doApply || this.scrollTop === undefined) return;
this.container.scrollTop = this.scrollTop;
}
}
......@@ -59,11 +81,12 @@ function upperBound(a, value, compare, lookup) {
}
function sortUnique(arr, f) {
function sortUnique(arr, f, equal) {
if (arr.length == 0) return arr;
arr = arr.sort(f);
let ret = [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i-1] !== arr[i]) {
if (!equal(arr[i-1], arr[i])) {
ret.push(arr[i]);
}
}
......@@ -78,3 +101,8 @@ function partial(f) {
f.apply(this, arguments1.concat(arguments2));
}
}
function isIterable(obj) {
return obj != null && obj != undefined
&& typeof obj != 'string' && typeof obj[Symbol.iterator] === 'function';
}
......@@ -6,9 +6,9 @@
class View {
constructor(id, broker) {
this.divElement = d3.select("#" + id);
this.divNode = this.divElement[0][0];
this.parentNode = this.divNode.parentNode;
this.container = document.getElementById(id);
this.divNode = this.createViewElement();
this.divElement = d3.select(this.divNode);
}
isScrollable() {
......@@ -16,7 +16,7 @@ class View {
}
show(data, rememberedSelection) {
this.parentNode.appendChild(this.divElement[0][0]);
this.container.appendChild(this.divElement.node());
this.initializeContent(data, rememberedSelection);
this.divElement.attr(VISIBILITY, 'visible');
}
......@@ -24,7 +24,7 @@ class View {
hide() {
this.divElement.attr(VISIBILITY, 'hidden');
this.deleteContent();
this.parentNode.removeChild(this.divNode);
this.container.removeChild(this.divNode);
}
detachSelection() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment