pxt-core
Version:
Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors
1,165 lines • 498 kB
JavaScript
// Helpers designed to help to make a simulator accessible.
var pxsim;
(function (pxsim) {
var accessibility;
(function (accessibility) {
let liveRegion;
let keydownListenerAdded = false;
function makeFocusable(elem) {
elem.setAttribute("focusable", "true");
elem.setAttribute("tabindex", "0");
}
accessibility.makeFocusable = makeFocusable;
function getGlobalAction(e) {
const isMac = window.navigator && /Mac/i.test(window.navigator.platform);
const meta = isMac ? e.metaKey : e.ctrlKey;
if (e.key === "Escape") {
e.preventDefault();
return "escape";
}
else if (e.key === "/" && meta) {
e.preventDefault();
return "togglekeyboardcontrolshelp";
}
else if (e.key === "b" && meta) {
e.preventDefault();
return "toggleareamenu";
}
return null;
}
accessibility.getGlobalAction = getGlobalAction;
function postKeyboardEvent() {
if (keydownListenerAdded) {
return;
}
keydownListenerAdded = true;
document.addEventListener("keydown", (e) => {
const action = getGlobalAction(e);
if (action) {
const message = {
type: "action",
action
};
pxsim.Runtime.postMessage(message);
}
});
}
accessibility.postKeyboardEvent = postKeyboardEvent;
function enableKeyboardInteraction(elem, handlerKeyDown, handlerKeyUp) {
if (handlerKeyDown) {
elem.addEventListener('keydown', (e) => {
const charCode = (typeof e.which == "number") ? e.which : e.keyCode;
if (charCode === 32 || charCode === 13) { // Enter or Space key
handlerKeyDown();
}
});
}
if (handlerKeyUp) {
elem.addEventListener('keyup', (e) => {
const charCode = (typeof e.which == "number") ? e.which : e.keyCode;
if (charCode === 32 || charCode === 13) { // Enter or Space key
handlerKeyUp();
}
});
}
}
accessibility.enableKeyboardInteraction = enableKeyboardInteraction;
function setAria(elem, role, label) {
if (role && !elem.hasAttribute("role")) {
elem.setAttribute("role", role);
}
if (label && !elem.hasAttribute("aria-label")) {
elem.setAttribute("aria-label", label);
}
}
accessibility.setAria = setAria;
function setLiveContent(value) {
if (!liveRegion) {
let style = "position: absolute !important;" +
"display: block;" +
"visibility: visible;" +
"overflow: hidden;" +
"width: 1px;" +
"height: 1px;" +
"margin: -1px;" +
"border: 0;" +
"padding: 0;" +
"clip: rect(0 0 0 0);";
liveRegion = document.createElement("div");
liveRegion.setAttribute("role", "status");
liveRegion.setAttribute("aria-live", "polite");
liveRegion.setAttribute("aria-hidden", "false");
liveRegion.setAttribute("style", style);
document.body.appendChild(liveRegion);
}
if (liveRegion.textContent !== value) {
liveRegion.textContent = value;
}
}
accessibility.setLiveContent = setLiveContent;
})(accessibility = pxsim.accessibility || (pxsim.accessibility = {}));
})(pxsim || (pxsim = {}));
var pxsim;
(function (pxsim) {
const GROUND_COLOR = "blue";
const POWER_COLOR = "red";
const POWER5V_COLOR = "orange";
;
;
;
;
;
;
function isOnBreadboardBottom(location) {
let isBot = false;
if (typeof location !== "string" && location.type === "breadboard") {
let bbLoc = location;
let row = bbLoc.row;
isBot = 0 <= ["a", "b", "c", "d", "e"].indexOf(row);
}
return isBot;
}
const arrCount = (a) => a.reduce((p, n) => p + (n ? 1 : 0), 0);
const arrAny = (a) => arrCount(a) > 0;
function computePowerUsage(wire) {
let ends = [wire.start, wire.end];
let endIsGround = ends.map(e => e === "ground");
let endIsThreeVolt = ends.map(e => e === "threeVolt");
let endIsFiveVolt = ends.map(e => e === "fiveVolt");
let endIsBot = ends.map(e => isOnBreadboardBottom(e));
let hasGround = arrAny(endIsGround);
let hasThreeVolt = arrAny(endIsThreeVolt);
let hasFiveVolt = arrAny(endIsFiveVolt);
let hasBot = arrAny(endIsBot);
return {
topGround: hasGround && !hasBot,
topThreeVolt: hasThreeVolt && !hasBot,
topFiveVolt: hasFiveVolt && !hasBot,
bottomGround: hasGround && hasBot,
bottomThreeVolt: hasThreeVolt && hasBot,
bottomFiveVolt: hasFiveVolt && hasBot,
singleGround: hasGround,
singleThreeVolt: hasThreeVolt,
singleFiveVolt: hasFiveVolt
};
}
function mergePowerUsage(powerUsages) {
const finalPowerUsage = powerUsages.reduce((p, n) => ({
topGround: p.topGround || n.topGround,
topThreeVolt: p.topThreeVolt || n.topThreeVolt,
topFiveVolt: p.topFiveVolt || n.topFiveVolt,
bottomGround: p.bottomGround || n.bottomGround,
bottomThreeVolt: p.bottomThreeVolt || n.bottomThreeVolt,
bottomFiveVolt: p.bottomFiveVolt || n.bottomFiveVolt,
singleGround: n.singleGround ? p.singleGround === null : p.singleGround,
singleThreeVolt: n.singleThreeVolt ? p.singleThreeVolt === null : p.singleThreeVolt,
singleFiveVolt: n.singleFiveVolt ? p.singleFiveVolt === null : p.singleFiveVolt,
}), {
topGround: false,
topThreeVolt: false,
topFiveVolt: false,
bottomGround: false,
bottomThreeVolt: false,
bottomFiveVolt: false,
singleGround: null,
singleThreeVolt: null,
singleFiveVolt: null
});
if (finalPowerUsage.singleGround)
finalPowerUsage.topGround = finalPowerUsage.bottomGround = false;
if (finalPowerUsage.singleThreeVolt)
finalPowerUsage.topThreeVolt = finalPowerUsage.bottomThreeVolt = false;
if (finalPowerUsage.singleFiveVolt)
finalPowerUsage.topFiveVolt = finalPowerUsage.bottomFiveVolt = false;
return finalPowerUsage;
}
function copyDoubleArray(a) {
return a.map(b => b.map(p => p));
}
function merge2(a, b) {
let res = {};
for (let aKey in a)
res[aKey] = a[aKey];
for (let bKey in b)
res[bKey] = b[bKey];
return res;
}
function merge3(a, b, c) {
return merge2(merge2(a, b), c);
}
function readPin(arg) {
pxsim.U.assert(!!arg, "Invalid pin: " + arg);
const pin = /^(\w+)\.\s*(?:[a-z]*)?([A-Z][A-Z\d_]+)$/.exec(arg);
return pin ? pin[2] : undefined;
}
pxsim.readPin = readPin;
function mkReverseMap(map) {
let origKeys = [];
let origVals = [];
for (let key in map) {
origKeys.push(key);
origVals.push(map[key]);
}
let newMap = {};
for (let i = 0; i < origKeys.length; i++) {
let newKey = origVals[i];
let newVal = origKeys[i];
newMap[newKey] = newVal;
}
return newMap;
}
function isConnectedToBB(pin) {
return pin.orientation === "-Z" && pin.style === "male";
}
class Allocator {
constructor(opts) {
this.availablePowerPins = {
top: {
fiveVolt: pxsim.mkRange(26, 51).map(n => ({ type: "breadboard", row: "+", col: `${n}` })),
threeVolt: pxsim.mkRange(26, 51).map(n => ({ type: "breadboard", row: "+", col: `${n}` })),
ground: pxsim.mkRange(26, 51).map(n => ({ type: "breadboard", row: "-", col: `${n}` })),
},
bottom: {
fiveVolt: pxsim.mkRange(1, 26).map(n => ({ type: "breadboard", row: "+", col: `${n}` })),
threeVolt: pxsim.mkRange(1, 26).map(n => ({ type: "breadboard", row: "+", col: `${n}` })),
ground: pxsim.mkRange(1, 26).map(n => ({ type: "breadboard", row: "-", col: `${n}` })),
},
};
this.opts = opts;
}
allocPartIRs(def, name, bbFit) {
let partIRs = [];
const mkIR = (def, name, instPins, partParams) => {
let pinIRs = [];
for (let i = 0; i < def.numberOfPins; i++) {
let pinDef = def.pinDefinitions[i];
let pinTarget;
if (typeof pinDef.target === "string") {
pinTarget = pinDef.target;
}
else {
let instIdx = pinDef.target.pinInstantiationIdx;
if (!(!!instPins && instPins[instIdx] !== undefined)) {
pxsim.log(`error: parts no pin found for PinInstantiationIdx: ${instIdx}. (Is the part missing an ArgumentRole or "trackArgs=" annotations?)`);
return undefined;
}
pinTarget = instPins[instIdx];
}
let pinLoc = def.visual.pinLocations[i];
let adjustedY = bbFit.yOffset + pinLoc.y;
let relativeRowIdx = Math.round(adjustedY / def.visual.pinDistance);
let relativeYOffset = adjustedY - relativeRowIdx * def.visual.pinDistance;
let adjustedX = bbFit.xOffset + pinLoc.x;
let relativeColIdx = Math.round(adjustedX / def.visual.pinDistance);
let relativeXOffset = adjustedX - relativeColIdx * def.visual.pinDistance;
let pinBBFit = {
partRelativeRowIdx: relativeRowIdx,
partRelativeColIdx: relativeColIdx,
xOffset: relativeXOffset,
yOffset: relativeYOffset
};
pinIRs.push({
def: pinDef,
loc: pinLoc,
target: pinTarget,
bbFit: pinBBFit,
});
}
return {
name: name,
def: def,
pins: pinIRs,
partParams: partParams || {},
bbFit: bbFit
};
};
// support for multiple possible instantions
const instantiations = def.instantiations || [];
if (def.instantiation)
instantiations.push(def.instantiation);
instantiations.forEach(instantiation => {
if (instantiation.kind === "singleton") {
partIRs.push(mkIR(def, name));
}
else if (instantiation.kind === "function") {
let fnAlloc = instantiation;
let fnNms = fnAlloc.fullyQualifiedName.split(',');
let callsitesTrackedArgsHash = {};
fnNms.forEach(fnNm => { if (this.opts.fnArgs[fnNm])
this.opts.fnArgs[fnNm].forEach((targetArg) => { callsitesTrackedArgsHash[targetArg] = 1; }); });
let callsitesTrackedArgs = Object.keys(callsitesTrackedArgsHash);
if (!(!!callsitesTrackedArgs && !!callsitesTrackedArgs.length)) {
pxsim.log(`error: parts failed to read pin(s) from callsite for: ${fnNms}`);
return undefined;
}
callsitesTrackedArgs.forEach(fnArgsStr => {
const fnArgsSplit = fnArgsStr.split(",");
if (fnArgsSplit.length != fnAlloc.argumentRoles.length) {
pxsim.log(`error: parts mismatch between number of arguments at callsite (function name: ${fnNms}) vs number of argument roles in part definition (part: ${name}).`);
return;
}
let instPins = [];
let paramArgs = {};
fnArgsSplit.forEach((arg, idx) => {
let role = fnAlloc.argumentRoles[idx];
if (role.partParameter !== undefined) {
paramArgs[role.partParameter] = arg;
}
if (role.pinInstantiationIdx !== undefined) {
let instIdx = role.pinInstantiationIdx;
let pin = readPin(arg);
instPins[instIdx] = pin;
}
});
partIRs.push(mkIR(def, name, instPins, paramArgs));
});
}
});
return partIRs.filter(ir => !!ir);
}
computePartDimensions(def, name) {
let pinLocs = def.visual.pinLocations;
let pinDefs = def.pinDefinitions;
let numPins = def.numberOfPins;
pxsim.U.assert(pinLocs.length === numPins, `Mismatch between "numberOfPins" and length of "visual.pinLocations" for "${name}"`);
pxsim.U.assert(pinDefs.length === numPins, `Mismatch between "numberOfPins" and length of "pinDefinitions" for "${name}"`);
pxsim.U.assert(numPins > 0, `Part "${name}" has no pins`);
let pins = pinLocs.map((loc, idx) => merge3({ idx: idx }, loc, pinDefs[idx]));
let bbPins = pins.filter(p => p.orientation === "-Z");
let hasBBPins = bbPins.length > 0;
let pinDist = def.visual.pinDistance;
let xOff;
let yOff;
let colCount;
let rowCount;
if (hasBBPins) {
let refPin = bbPins[0];
let refPinColIdx = Math.ceil(refPin.x / pinDist);
let refPinRowIdx = Math.ceil(refPin.y / pinDist);
xOff = refPinColIdx * pinDist - refPin.x;
yOff = refPinRowIdx * pinDist - refPin.y;
colCount = Math.ceil((xOff + def.visual.width) / pinDist) + 1;
rowCount = Math.ceil((yOff + def.visual.height) / pinDist) + 1;
}
else {
colCount = Math.ceil(def.visual.width / pinDist);
rowCount = Math.ceil(def.visual.height / pinDist);
xOff = colCount * pinDist - def.visual.width;
yOff = rowCount * pinDist - def.visual.height;
}
return {
xOffset: xOff,
yOffset: yOff,
rowCount: rowCount,
colCount: colCount
};
}
allocColumns(colCounts) {
let partsCount = colCounts.length;
const totalColumnsCount = pxsim.visuals.BREADBOARD_MID_COLS; //TODO allow multiple breadboards
let totalSpaceNeeded = colCounts.map(d => d.colCount).reduce((p, n) => p + n, 0);
let extraSpace = totalColumnsCount - totalSpaceNeeded;
if (extraSpace <= 0) {
pxsim.log("Not enough breadboard space!");
//TODO
}
let padding = Math.floor(extraSpace / (partsCount - 1 + 2));
let partSpacing = padding; //Math.floor(extraSpace/(partsCount-1));
let totalPartPadding = extraSpace - partSpacing * (partsCount - 1);
let leftPadding = Math.floor(totalPartPadding / 2);
let rightPadding = Math.ceil(totalPartPadding / 2);
let nextAvailableCol = 1 + leftPadding;
let partStartCol = colCounts.map(part => {
let col = nextAvailableCol;
nextAvailableCol += part.colCount + partSpacing;
return col;
});
return partStartCol;
}
placeParts(parts) {
const totalRowsCount = pxsim.visuals.BREADBOARD_MID_ROWS + 2; // 10 letters + 2 for the middle gap
let startColumnIndices = this.allocColumns(parts.map(p => p.bbFit));
let startRowIndicies = parts.map(p => {
let extraRows = totalRowsCount - p.bbFit.rowCount;
let topPad = Math.floor(extraRows / 2);
let startIdx = topPad;
if (startIdx > 4)
startIdx = 4;
if (startIdx < 1)
startIdx = 1;
return startIdx;
});
let placements = parts.map((p, idx) => {
let row = startRowIndicies[idx];
let col = startColumnIndices[idx];
return merge2({ startColumnIdx: col, startRowIdx: row }, p);
});
return placements;
}
nextColor() {
if (!this.availableWireColors || this.availableWireColors.length <= 0) {
this.availableWireColors = pxsim.visuals.GPIO_WIRE_COLORS.map(c => c);
}
return this.availableWireColors.pop();
}
allocWireIRs(part) {
let groupToColor = [];
let wires = part.pins.map((pin, pinIdx) => {
let end = pin.target;
let start;
let colIdx = part.startColumnIdx + pin.bbFit.partRelativeColIdx;
let colName = pxsim.visuals.getColumnName(colIdx);
let pinRowIdx = part.startRowIdx + pin.bbFit.partRelativeRowIdx;
if (pinRowIdx >= 7) //account for middle gap
pinRowIdx -= 2;
if (isConnectedToBB(pin.def)) {
//make a wire from bb top or bottom to target
let connectedToTop = pinRowIdx < 5;
let rowName = connectedToTop ? "j" : "a";
start = {
type: "breadboard",
row: rowName,
col: colName,
style: pin.def.style
};
}
else {
//make a wire directly from pin to target
let rowName = pxsim.visuals.getRowName(pinRowIdx);
start = {
type: "breadboard",
row: rowName,
col: colName,
xOffset: pin.bbFit.xOffset / part.def.visual.pinDistance,
yOffset: pin.bbFit.yOffset / part.def.visual.pinDistance,
style: pin.def.style
};
}
let color;
if (end === "ground") {
color = GROUND_COLOR;
}
else if (end === "threeVolt") {
color = POWER_COLOR;
}
else if (end === "fiveVolt") {
color = POWER5V_COLOR;
}
else if (typeof pin.def.colorGroup === "number") {
if (groupToColor[pin.def.colorGroup]) {
color = groupToColor[pin.def.colorGroup];
}
else {
color = groupToColor[pin.def.colorGroup] = this.nextColor();
}
}
else {
color = this.nextColor();
}
return {
start: start,
end: end,
color: color,
pinIdx: pinIdx,
};
});
return merge2(part, { wires: wires });
}
allocLocation(location, opts) {
if (location === "ground" || location === "threeVolt" || location == "fiveVolt") {
//special case if there is only a single ground or three volt pin in the whole build
if (location === "ground" && this.powerUsage.singleGround) {
let boardGroundPin = this.getBoardGroundPin();
return { type: "dalboard", pin: boardGroundPin };
}
else if (location === "threeVolt" && this.powerUsage.singleThreeVolt) {
let boardThreeVoltPin = this.getBoardThreeVoltPin();
return { type: "dalboard", pin: boardThreeVoltPin };
}
else if (location === "fiveVolt" && this.powerUsage.singleFiveVolt) {
let boardFiveVoltPin = this.getBoardFiveVoltPin();
return { type: "dalboard", pin: boardFiveVoltPin };
}
pxsim.U.assert(!!opts.referenceBBPin);
let nearestCoord = this.opts.getBBCoord(opts.referenceBBPin);
let firstTopAndBot = [
this.availablePowerPins.top.ground[0] || this.availablePowerPins.top.threeVolt[0],
this.availablePowerPins.bottom.ground[0] || this.availablePowerPins.bottom.threeVolt[0]
].map(loc => {
return this.opts.getBBCoord(loc);
});
if (!firstTopAndBot[0] || !firstTopAndBot[1]) {
pxsim.debug(`No more available "${location}" locations!`);
//TODO
}
let nearTop = pxsim.visuals.findClosestCoordIdx(nearestCoord, firstTopAndBot) == 0;
let barPins;
if (nearTop) {
if (location === "ground") {
barPins = this.availablePowerPins.top.ground;
}
else if (location === "threeVolt") {
barPins = this.availablePowerPins.top.threeVolt;
}
else if (location === "fiveVolt") {
barPins = this.availablePowerPins.top.fiveVolt;
}
}
else {
if (location === "ground") {
barPins = this.availablePowerPins.bottom.ground;
}
else if (location === "threeVolt") {
barPins = this.availablePowerPins.bottom.threeVolt;
}
else if (location === "fiveVolt") {
barPins = this.availablePowerPins.bottom.fiveVolt;
}
}
let pinCoords = barPins.map(rowCol => {
return this.opts.getBBCoord(rowCol);
});
let closestPinIdx = pxsim.visuals.findClosestCoordIdx(nearestCoord, pinCoords);
let pin = barPins[closestPinIdx];
if (nearTop) {
this.availablePowerPins.top.ground.splice(closestPinIdx, 1);
this.availablePowerPins.top.threeVolt.splice(closestPinIdx, 1);
}
else {
this.availablePowerPins.bottom.ground.splice(closestPinIdx, 1);
this.availablePowerPins.bottom.threeVolt.splice(closestPinIdx, 1);
}
return pin;
}
else if (location.type === "breadboard") {
return location;
}
else if (location === "MOSI" || location === "MISO" || location === "SCK") {
if (!this.opts.boardDef.spiPins)
pxsim.debug("No SPI pin mappings found!");
let pin = this.opts.boardDef.spiPins[location];
return { type: "dalboard", pin: pin };
}
else if (location === "SDA" || location === "SCL") {
if (!this.opts.boardDef.i2cPins)
pxsim.debug("No I2C pin mappings found!");
let pin = this.opts.boardDef.i2cPins[location];
return { type: "dalboard", pin: pin };
}
else {
//it must be a MicrobitPin
pxsim.U.assert(typeof location === "string", "Unknown location type: " + location);
let mbPin = location;
let boardPin = this.opts.boardDef.gpioPinMap[mbPin] || mbPin;
if (!boardPin) { // this pin is internal
pxsim.debug(`unknown pin location for ${mbPin}`);
return undefined;
}
return { type: "dalboard", pin: boardPin };
}
}
getBoardGroundPin() {
let pin = this.opts.boardDef.groundPins && this.opts.boardDef.groundPins[0] || null;
if (!pin) {
pxsim.debug("No available ground pin on board!");
//TODO
}
return pin;
}
getBoardThreeVoltPin() {
let pin = this.opts.boardDef.threeVoltPins && this.opts.boardDef.threeVoltPins[0] || null;
if (!pin) {
pxsim.debug("No available 3.3V pin on board!");
//TODO
}
return pin;
}
getBoardFiveVoltPin() {
let pin = this.opts.boardDef.fiveVoltPins && this.opts.boardDef.fiveVoltPins[0] || null;
if (!pin) {
pxsim.debug("No available 5V pin on board!");
//TODO
}
return pin;
}
allocPowerWires(powerUsage) {
let boardGroundPin = this.getBoardGroundPin();
let threeVoltPin = this.getBoardThreeVoltPin();
let fiveVoltPin = this.getBoardFiveVoltPin();
const topLeft = { type: "breadboard", row: "-", col: "26" };
const botLeft = { type: "breadboard", row: "-", col: "1" };
const topRight = { type: "breadboard", row: "-", col: "50" };
const botRight = { type: "breadboard", row: "-", col: "25" };
let top, bot;
if (this.opts.boardDef.attachPowerOnRight) {
top = topRight;
bot = botRight;
}
else {
top = topLeft;
bot = botLeft;
}
let groundWires = [];
let threeVoltWires = [];
let fiveVoltWires = [];
if (powerUsage.bottomGround && powerUsage.topGround) {
//bb top - <==> bb bot -
groundWires.push({
start: this.allocLocation("ground", { referenceBBPin: top }),
end: this.allocLocation("ground", { referenceBBPin: bot }),
color: GROUND_COLOR,
});
}
if (powerUsage.topGround) {
//board - <==> bb top -
groundWires.push({
start: this.allocLocation("ground", { referenceBBPin: top }),
end: { type: "dalboard", pin: boardGroundPin },
color: GROUND_COLOR,
});
}
else if (powerUsage.bottomGround) {
//board - <==> bb bot -
groundWires.push({
start: this.allocLocation("ground", { referenceBBPin: bot }),
end: { type: "dalboard", pin: boardGroundPin },
color: GROUND_COLOR,
});
}
if (powerUsage.bottomThreeVolt && powerUsage.bottomGround) {
//bb top + <==> bb bot +
threeVoltWires.push({
start: this.allocLocation("threeVolt", { referenceBBPin: top }),
end: this.allocLocation("threeVolt", { referenceBBPin: bot }),
color: POWER_COLOR,
});
}
else if (powerUsage.bottomFiveVolt && powerUsage.bottomGround) {
//bb top + <==> bb bot +
fiveVoltWires.push({
start: this.allocLocation("fiveVolt", { referenceBBPin: top }),
end: this.allocLocation("fiveVolt", { referenceBBPin: bot }),
color: POWER5V_COLOR,
});
}
if (powerUsage.topThreeVolt) {
//board + <==> bb top +
threeVoltWires.push({
start: this.allocLocation("threeVolt", { referenceBBPin: top }),
end: { type: "dalboard", pin: threeVoltPin },
color: POWER_COLOR,
});
}
else if (powerUsage.bottomThreeVolt) {
//board + <==> bb bot +
threeVoltWires.push({
start: this.allocLocation("threeVolt", { referenceBBPin: bot }),
end: { type: "dalboard", pin: threeVoltPin },
color: POWER5V_COLOR,
});
}
if (powerUsage.topFiveVolt && !powerUsage.topThreeVolt) {
//board + <==> bb top +
fiveVoltWires.push({
start: this.allocLocation("fiveVolt", { referenceBBPin: top }),
end: { type: "dalboard", pin: fiveVoltPin },
color: POWER_COLOR,
});
}
else if (powerUsage.bottomFiveVolt && !powerUsage.bottomThreeVolt) {
//board + <==> bb bot +
fiveVoltWires.push({
start: this.allocLocation("fiveVolt", { referenceBBPin: bot }),
end: { type: "dalboard", pin: fiveVoltPin },
color: POWER5V_COLOR,
});
}
let assembly = [];
if (groundWires.length > 0)
assembly.push({ wireIndices: groundWires.map((w, i) => i) });
let numGroundWires = groundWires.length;
if (threeVoltWires.length > 0)
assembly.push({
wireIndices: threeVoltWires.map((w, i) => i + numGroundWires)
});
if (fiveVoltWires.length > 0)
assembly.push({
wireIndices: threeVoltWires.map((w, i) => i + numGroundWires + threeVoltWires.length)
});
return {
wires: groundWires.concat(threeVoltWires).concat(fiveVoltWires),
assembly: assembly
};
}
allocWire(wireIR) {
const ends = [wireIR.start, wireIR.end];
const endIsPower = ends.map(e => e === "ground" || e === "threeVolt" || e === "fiveVolt");
//allocate non-power first so we know the nearest pin for the power end
let endInsts = ends.map((e, idx) => !endIsPower[idx] ? this.allocLocation(e, {}) : undefined);
//allocate power pins closest to the other end of the wire
endInsts = endInsts.map((e, idx) => {
if (e)
return e;
const locInst = endInsts[1 - idx]; // non-power end
const l = this.allocLocation(ends[idx], {
referenceBBPin: locInst,
});
return l;
});
// one of the pins is not accessible
if (!endInsts[0] || !endInsts[1])
return undefined;
return { start: endInsts[0], end: endInsts[1], color: wireIR.color };
}
allocPart(ir) {
let bbConnections = ir.pins
.filter(p => isConnectedToBB(p.def))
.map(p => {
let rowIdx = ir.startRowIdx + p.bbFit.partRelativeRowIdx;
if (rowIdx >= 7) //account for middle gap
rowIdx -= 2;
let rowName = pxsim.visuals.getRowName(rowIdx);
let colIdx = ir.startColumnIdx + p.bbFit.partRelativeColIdx;
let colName = pxsim.visuals.getColumnName(colIdx);
return {
type: "breadboard",
row: rowName,
col: colName,
};
});
let part = {
name: ir.name,
visual: ir.def.visual,
bbFit: ir.bbFit,
startColumnIdx: ir.startColumnIdx,
startRowIdx: ir.startRowIdx,
breadboardConnections: bbConnections,
params: ir.partParams,
simulationBehavior: ir.def.simulationBehavior
};
return part;
}
allocAll() {
let partNmAndDefs = this.opts.partsList
.map(partName => { return { name: partName, def: this.opts.partDefs[partName] }; })
.filter(d => !!d.def);
if (partNmAndDefs.length > 0) {
let dimensions = partNmAndDefs.map(nmAndPart => this.computePartDimensions(nmAndPart.def, nmAndPart.name));
let partIRs = [];
partNmAndDefs.forEach((nmAndDef, idx) => {
let dims = dimensions[idx];
let irs = this.allocPartIRs(nmAndDef.def, nmAndDef.name, dims);
partIRs = partIRs.concat(irs);
});
const partPlacements = this.placeParts(partIRs);
const partsAndWireIRs = partPlacements.map(p => this.allocWireIRs(p));
const allWireIRs = partsAndWireIRs.map(p => p.wires).reduce((p, n) => p.concat(n), []);
const allPowerUsage = allWireIRs.map(w => computePowerUsage(w));
this.powerUsage = mergePowerUsage(allPowerUsage);
const basicWires = this.allocPowerWires(this.powerUsage);
const partsAndWires = partsAndWireIRs.map((irs, idx) => {
const part = this.allocPart(irs);
const wires = irs.wires.map(w => this.allocWire(w));
if (wires.some(w => !w))
return undefined;
const pinIdxToWireIdx = [];
irs.wires.forEach((wIR, idx) => {
pinIdxToWireIdx[wIR.pinIdx] = idx;
});
const assembly = irs.def.assembly.map(stepDef => {
return {
part: stepDef.part,
wireIndices: (stepDef.pinIndices || []).map(i => pinIdxToWireIdx[i])
};
});
return {
part: part,
wires: wires,
assembly: assembly
};
}).filter(p => !!p);
const all = [basicWires].concat(partsAndWires)
.filter(pw => pw.assembly && pw.assembly.length); // only keep steps with something to do
// hide breadboard if not used
const hideBreadboard = !all.some(r => (r.part && r.part.breadboardConnections && r.part.breadboardConnections.length > 0)
|| r.wires && r.wires.some(w => (w.end.type == "breadboard" && w.end.style != "croc") || (w.start.type == "breadboard" && w.start.style != "croc")));
return {
partsAndWires: all,
wires: [],
parts: [],
hideBreadboard
};
}
else {
return {
partsAndWires: [],
wires: [],
parts: []
};
}
}
}
function allocateDefinitions(opts) {
return new Allocator(opts).allocAll();
}
pxsim.allocateDefinitions = allocateDefinitions;
})(pxsim || (pxsim = {}));
/// <reference path="../localtypings/vscode-debug-protocol.d.ts" />
/**
* Heavily adapted from https://github.com/microsoft/vscode-debugadapter-node
* and altered to run in a browser and communcate via JSON over a websocket
* rather than through stdin and stdout
*/
var pxsim;
(function (pxsim) {
var protocol;
(function (protocol) {
class Message {
constructor(type) {
this.seq = 0;
this.type = type;
}
}
protocol.Message = Message;
class Response extends Message {
constructor(request, message) {
super('response');
this.request_seq = request.seq;
this.command = request.command;
if (message) {
this.success = false;
this.message = message;
}
else {
this.success = true;
}
}
}
protocol.Response = Response;
class Event extends Message {
constructor(event, body) {
super('event');
this.event = event;
if (body) {
this.body = body;
}
}
}
protocol.Event = Event;
class Source {
constructor(name, path, id = 0, origin, data) {
this.name = name;
this.path = path;
this.sourceReference = id;
if (origin) {
this.origin = origin;
}
if (data) {
this.adapterData = data;
}
}
}
protocol.Source = Source;
class Scope {
constructor(name, reference, expensive = false) {
this.name = name;
this.variablesReference = reference;
this.expensive = expensive;
}
}
protocol.Scope = Scope;
class StackFrame {
constructor(i, nm, src, ln = 0, col = 0) {
this.id = i;
this.source = src;
this.line = ln;
this.column = col;
this.name = nm;
}
}
protocol.StackFrame = StackFrame;
class Thread {
constructor(id, name) {
this.id = id;
if (name) {
this.name = name;
}
else {
this.name = 'Thread #' + id;
}
}
}
protocol.Thread = Thread;
class Variable {
constructor(name, value, ref = 0, indexedVariables, namedVariables) {
this.name = name;
this.value = value;
this.variablesReference = ref;
if (typeof namedVariables === 'number') {
this.namedVariables = namedVariables;
}
if (typeof indexedVariables === 'number') {
this.indexedVariables = indexedVariables;
}
}
}
protocol.Variable = Variable;
class Breakpoint {
constructor(verified, line, column, source) {
this.verified = verified;
const e = this;
if (typeof line === 'number') {
e.line = line;
}
if (typeof column === 'number') {
e.column = column;
}
if (source) {
e.source = source;
}
}
}
protocol.Breakpoint = Breakpoint;
class Module {
constructor(id, name) {
this.id = id;
this.name = name;
}
}
protocol.Module = Module;
class CompletionItem {
constructor(label, start, length = 0) {
this.label = label;
this.start = start;
this.length = length;
}
}
protocol.CompletionItem = CompletionItem;
class StoppedEvent extends Event {
constructor(reason, threadId, exception_text = null) {
super('stopped');
this.body = {
reason: reason,
threadId: threadId
};
if (exception_text) {
const e = this;
e.body.text = exception_text;
}
}
}
protocol.StoppedEvent = StoppedEvent;
class ContinuedEvent extends Event {
constructor(threadId, allThreadsContinued) {
super('continued');
this.body = {
threadId: threadId
};
if (typeof allThreadsContinued === 'boolean') {
this.body.allThreadsContinued = allThreadsContinued;
}
}
}
protocol.ContinuedEvent = ContinuedEvent;
class InitializedEvent extends Event {
constructor() {
super('initialized');
}
}
protocol.InitializedEvent = InitializedEvent;
class TerminatedEvent extends Event {
constructor(restart) {
super('terminated');
if (typeof restart === 'boolean') {
const e = this;
e.body = {
restart: restart
};
}
}
}
protocol.TerminatedEvent = TerminatedEvent;
class OutputEvent extends Event {
constructor(output, category = 'console', data) {
super('output');
this.body = {
category: category,
output: output
};
if (data !== undefined) {
this.body.data = data;
}
}
}
protocol.OutputEvent = OutputEvent;
class ThreadEvent extends Event {
constructor(reason, threadId) {
super('thread');
this.body = {
reason: reason,
threadId: threadId
};
}
}
protocol.ThreadEvent = ThreadEvent;
class BreakpointEvent extends Event {
constructor(reason, breakpoint) {
super('breakpoint');
this.body = {
reason: reason,
breakpoint: breakpoint
};
}
}
protocol.BreakpointEvent = BreakpointEvent;
class ModuleEvent extends Event {
constructor(reason, module) {
super('module');
this.body = {
reason: reason,
module: module
};
}
}
protocol.ModuleEvent = ModuleEvent;
class ProtocolServer {
constructor() {
this._pendingRequests = {};
}
start(host) {
this._sequence = 1;
this.host = host;
this.host.onData(msg => {
if (msg.type === 'request') {
this.dispatchRequest(msg);
}
else if (msg.type === 'response') {
const response = msg;
const clb = this._pendingRequests[response.seq];
if (clb) {
delete this._pendingRequests[response.seq];
clb(response);
}
}
});
}
stop() {
if (this.host) {
this.host.close();
}
}
sendEvent(event) {
this.send('event', event);
}
sendResponse(response) {
if (response.seq > 0) {
pxsim.error(`attempt to send more than one response for command ${response.command}`);
}
else {
this.send('response', response);
}
}
sendRequest(command, args, timeout, cb) {
const request = {
command: command
};
if (args && Object.keys(args).length > 0) {
request.arguments = args;
}
this.send('request', request);
if (cb) {
this._pendingRequests[request.seq] = cb;
const timer = setTimeout(() => {
clearTimeout(timer);
const clb = this._pendingRequests[request.seq];
if (clb) {
delete this._pendingRequests[request.seq];
clb(new protocol.Response(request, 'timeout'));
}
}, timeout);
}
}
send(typ, message) {
message.type = typ;
message.seq = this._sequence++;
if (this.host) {
const json = JSON.stringify(message);
this.host.send(json);
}
}
// ---- protected ----------------------------------------------------------
dispatchRequest(request) {
}
}
protocol.ProtocolServer = ProtocolServer;
class DebugSession extends ProtocolServer {
constructor() {
super(...arguments);
this._debuggerLinesStartAt1 = false;
this._debuggerColumnsStartAt1 = false;
this._clientLinesStartAt1 = true;
this._clientColumnsStartAt1 = true;
}
shutdown() {
}
dispatchRequest(request) {
const response = new protocol.Response(request);
try {
if (request.command === 'initialize') {
let args = request.arguments;
if (typeof args.linesStartAt1 === 'boolean') {
this._clientLinesStartAt1 = args.linesStartAt1;
}
if (typeof args.columnsStartAt1 === 'boolean') {
this._clientColumnsStartAt1 = args.columnsStartAt1;
}
if (args.pathFormat !== 'path') {
this.sendErrorResponse(response, 2018, 'debug adapter only supports native paths', null);
}
else {
const initializeResponse = response;
initializeResponse.body = {};
this.initializeRequest(initializeResponse, args);
}
}
else if (request.command === 'launch') {
this.launchRequest(response, request.arguments);
}
else if (request.command === 'attach') {
this.attachRequest(response, request.arguments);
}
else if (request.command === 'disconnect') {
this.disconnectRequest(response, request.arguments);
}
else if (request.command === 'setBreakpoints') {
this.setBreakPointsRequest(response, request.arguments);
}
else if (request.command === 'setFunctionBreakpoints') {
this.setFunctionBreakPointsRequest(response, request.arguments);
}
else if (request.command === 'setExceptionBreakpoints') {
this.setExceptionBreakPointsRequest(response, request.arguments);
}
else if (request.command === 'configurationDone') {
this.configurationDoneRequest(response, request.arguments);
}
else if (request.command === 'continue') {
this.continueRequest(response, request.arguments);
}
else if (request.command === 'next') {
this.nextRequest(response, request.arguments);
}
else if (request.command === 'stepIn') {
this.stepInRequest(response, request.arguments);
}
else if (request.command === 'stepOut') {
this.stepOutRequest(response, request.arguments);
}
else if (request.command === 'stepBack') {
this.stepBackRequest(response, request.arguments);
}
else if (request.command === 'restartFrame') {
this.restartFrameRequest(response, request.arguments);
}
else if (request.command === 'goto') {
this.gotoRequest(respons