220 lines · javascript
1//===-- HTMLLogger.js -----------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9// Based on selected objects, hide/show sections & populate data from templates.10//11// For example, if the selection is {bb="BB4", elt="BB4.6" iter="BB4:2"}:12// - show the "block" and "element" sections13// - re-render templates within these sections (if selection changed)14// - apply "bb-select" to items with class class "BB4", etc15let selection = {};16function updateSelection(changes, data) {17 Object.assign(selection, changes);18 19 data = Object.create(data);20 data.selection = selection;21 for (root of document.querySelectorAll('[data-selection]'))22 updateSection(root, data);23 24 for (var k in changes)25 applyClassIf(k + '-select', classSelector(changes[k]));26}27 28// Given <section data-selection="x,y">:29// - hide section if selections x or y are null30// - re-render templates if x or y have changed31function updateSection(root, data) {32 let changed = root.selection == null;33 root.selection ||= {};34 for (key of root.dataset.selection.split(',')) {35 if (!key) continue;36 if (data.selection[key] != root.selection[key]) {37 root.selection[key] = data.selection[key];38 changed = true;39 }40 if (data.selection[key] == null) {41 root.hidden = true;42 return;43 }44 }45 if (changed) {46 root.hidden = false;47 for (tmpl of root.getElementsByTagName('template'))48 reinflate(tmpl, data);49 }50}51 52// Expands template `tmpl` based on input `data`:53// - interpolates {{expressions}} in text and attributes54// - <template> tags can modify expansion: if, for etc55// Outputs to `parent` element, inserting before `next`.56function inflate(tmpl, data, parent, next) {57 // We use eval() as our expression language in templates!58 // The templates are static and trusted.59 let evalExpr = (expr, data) => eval('with (data) { ' + expr + ' }');60 let interpolate = (str, data) =>61 str.replace(/\{\{(.*?)\}\}/g, (_, expr) => evalExpr(expr, data))62 // Anything other than <template> tag: copy, interpolate, recursively inflate.63 if (tmpl.nodeName != 'TEMPLATE') {64 let clone = tmpl.cloneNode();65 clone.inflated = true;66 if (clone instanceof Text)67 clone.textContent = interpolate(clone.textContent, data);68 if (clone instanceof Element) {69 for (attr of clone.attributes)70 attr.value = interpolate(attr.value, data);71 for (c of tmpl.childNodes)72 inflate(c, data, clone, /*next=*/null);73 }74 return parent.insertBefore(clone, next);75 }76 // data-use="xyz": use <template id="xyz"> instead. (Allows recursion.)77 if ('use' in tmpl.dataset)78 return inflate(document.getElementById(tmpl.dataset.use), data, parent, next);79 // <template> tag handling. Base case: recursively inflate.80 function handle(data) {81 for (c of tmpl.content.childNodes)82 inflate(c, data, parent, next);83 }84 // Directives on <template> tags modify behavior.85 const directives = {86 // data-for="x in expr": expr is enumerable, bind x to each in turn87 'for': (nameInExpr, data, proceed) => {88 let [name, expr] = nameInExpr.split(' in ');89 let newData = Object.create(data);90 let index = 0;91 for (val of evalExpr(expr, data) || []) {92 newData[name] = val;93 newData[name + '_index'] = index++;94 proceed(newData);95 }96 },97 // data-if="expr": only include contents if expression is truthy98 'if': (expr, data, proceed) => { if (evalExpr(expr, data)) proceed(data); },99 // data-let="x = expr": bind x to value of expr100 'let': (nameEqExpr, data, proceed) => {101 let [name, expr] = nameEqExpr.split(' = ');102 let newData = Object.create(data);103 newData[name] = evalExpr(expr, data);104 proceed(newData);105 },106 }107 // Compose directive handlers on top of the base handler.108 for (let [dir, value] of Object.entries(tmpl.dataset).reverse()) {109 if (dir in directives) {110 let proceed = handle;111 handle = (data) => directives[dir](value, data, proceed);112 }113 }114 handle(data);115}116// Expand a template, after first removing any prior expansion of it.117function reinflate(tmpl, data) {118 // Clear previously rendered template contents.119 while (tmpl.nextSibling && tmpl.nextSibling.inflated)120 tmpl.parentNode.removeChild(tmpl.nextSibling);121 inflate(tmpl, data, tmpl.parentNode, tmpl.nextSibling);122}123 124// Handle a mouse event on a region containing selectable items.125// This might end up changing the hover state or the selection state.126//127// targetSelector describes what target HTML element is selectable.128// targetToID specifies how to determine the selection from it:129// hover: a function from target to the class name to highlight130// bb: a function from target to the basic-block name to select (BB4)131// elt: a function from target to the CFG element name to select (BB4.5)132// iter: a function from target to the BB iteration to select (BB4:2)133// If an entry is missing, the selection is unmodified.134// If an entry is null, the selection is always cleared.135function mouseEventHandler(event, targetSelector, targetToID, data) {136 var target = event.type == "mouseout" ? null : event.target.closest(targetSelector);137 let selTarget = k => (target && targetToID[k]) ? targetToID[k](target) : null;138 if (event.type == "click") {139 let newSel = {};140 for (var k in targetToID) {141 if (k == 'hover') continue;142 let t = selTarget(k);143 newSel[k] = t;144 }145 updateSelection(newSel, data);146 } else if ("hover" in targetToID) {147 applyClassIf("hover", classSelector(selTarget("hover")));148 }149}150function watch(rootSelector, targetSelector, targetToID, data) {151 var root = document.querySelector(rootSelector);152 for (event of ['mouseout', 'mousemove', 'click'])153 root.addEventListener(event, e => mouseEventHandler(e, targetSelector, targetToID, data));154}155function watchSelection(data) {156 let lastIter = (bb) => `${bb}:${data.cfg[bb].iters}`;157 watch('#code', '.c', {158 hover: e => e.dataset.elt,159 bb: e => e.dataset.bb,160 elt: e => e.dataset.elt,161 // If we're already viewing an iteration of this BB, stick with the same.162 iter: e => (selection.iter && selection.bb == e.dataset.bb) ? selection.iter : lastIter(e.dataset.bb),163 }, data);164 watch('#cfg', '.bb', {165 hover: e => e.id,166 bb: e => e.id,167 elt: e => e.id + ".0",168 iter: e => lastIter(e.id),169 }, data);170 watch('#timeline', '.entry', {171 hover: e => [e.id, e.dataset.bb],172 bb: e => e.dataset.bb,173 elt: e => e.dataset.bb + ".0",174 iter: e => e.id,175 }, data);176 watch('#bb-elements', 'tr', {177 hover: e => e.id,178 elt: e => e.id,179 }, data);180 watch('#iterations', '.chooser', {181 hover: e => e.dataset.iter,182 iter: e => e.dataset.iter,183 }, data);184 updateSelection({}, data);185}186function applyClassIf(cls, query) {187 document.querySelectorAll('.' + cls).forEach(elt => elt.classList.remove(cls));188 document.querySelectorAll(query).forEach(elt => elt.classList.add(cls));189}190// Turns a class name into a CSS selector matching it, with some wrinkles:191// - we treat id="foo" just like class="foo" to avoid repetition in the HTML192// - cls can be an array of strings, we match them all193function classSelector(cls) {194 if (cls == null) return null;195 if (Array.isArray(cls)) return cls.map(classSelector).join(', ');196 var escaped = cls.replace('.', '\\.').replace(':', '\\:');197 // don't require id="foo" class="foo"198 return '.' + escaped + ", #" + escaped;199}200 201// Add a stylesheet defining colors for n basic blocks.202function addBBColors(n) {203 let sheet = new CSSStyleSheet();204 // hex values to subtract from fff to get a base color205 options = [0x001, 0x010, 0x011, 0x100, 0x101, 0x110, 0x111];206 function color(hex) {207 return "#" + hex.toString(16).padStart(3, "0");208 }209 function add(selector, property, hex) {210 sheet.insertRule(`${selector} { ${property}: ${color(hex)}; }`)211 }212 for (var i = 0; i < n; ++i) {213 let opt = options[i%options.length];214 add(`.B${i}`, 'background-color', 0xfff - 2*opt);215 add(`#B${i} polygon`, 'fill', 0xfff - 2*opt);216 add(`#B${i} polygon`, 'stroke', 0x888 - 4*opt);217 }218 document.adoptedStyleSheets.push(sheet);219}220