let allRegions = [];
let currentSort = { column: null, direction: "asc" };
// Dark Mode Toggle
function toggleDarkMode() {
const html = document.documentElement;
const sunIcon = document.getElementById("theme-icon-sun");
const moonIcon = document.getElementById("theme-icon-moon");
if (html.classList.contains("dark")) {
html.classList.remove("dark");
html.classList.add("light");
sunIcon.classList.add("hidden");
moonIcon.classList.remove("hidden");
localStorage.setItem("theme", "light");
} else {
html.classList.remove("light");
html.classList.add("dark");
sunIcon.classList.remove("hidden");
moonIcon.classList.add("hidden");
localStorage.setItem("theme", "dark");
}
}
// Initialize theme from localStorage
(function initTheme() {
const savedTheme = localStorage.getItem("theme") || "light";
const html = document.documentElement;
const sunIcon = document.getElementById("theme-icon-sun");
const moonIcon = document.getElementById("theme-icon-moon");
if (savedTheme === "dark") {
html.classList.remove("light");
html.classList.add("dark");
if (sunIcon) sunIcon.classList.remove("hidden");
if (moonIcon) moonIcon.classList.add("hidden");
} else {
html.classList.remove("dark");
html.classList.add("light");
if (sunIcon) sunIcon.classList.add("hidden");
if (moonIcon) moonIcon.classList.remove("hidden");
}
})();
// Parse Logic
function parseMemory() {
const input = document.getElementById("input").value;
if (!input.trim()) return;
const lines = input.split("\n");
const regions = [];
let currentRegion = null;
// Updated Regex to be more robust
// Captures: 1: Start, 2: End, 3: Perms, 4: Name (optional)
const regionRegex =
/\[(0x[0-9a-fA-F]+)-(0x[0-9a-fA-F]+)\)\s+([r\-][w\-][x\-][s\-]?)(?:\s+(.*))?/i;
const dirtyRegex = /Dirty pages:\s+(.+)/i;
for (let line of lines) {
line = line.trim();
if (!line) continue;
const regionMatch = line.match(regionRegex);
if (regionMatch) {
const start = regionMatch[1];
const end = regionMatch[2];
const perms = regionMatch[3];
const name = regionMatch[4] || "";
currentRegion = {
startStr: start, // Keep string for display
endStr: end, // Keep string for display
startBig: BigInt(start), // Use BigInt for calculation
endBig: BigInt(end), // Use BigInt for calculation
perms,
name,
dirtyPages: [],
dirtyCount: 0,
};
// Calculate size safely using BigInt
currentRegion.size = currentRegion.endBig - currentRegion.startBig;
regions.push(currentRegion);
} else if (currentRegion) {
// Check for metadata lines associated with the previous region
const dirtyMatch = line.match(dirtyRegex);
if (dirtyMatch) {
const pages = dirtyMatch[1].split(",").map((p) => p.trim());
currentRegion.dirtyPages = pages;
currentRegion.dirtyCount = pages.length;
}
}
}
allRegions = regions;
updateStats(regions);
document.getElementById("stats").classList.remove("hidden");
document.getElementById("stats").classList.add("grid");
document.getElementById("filter-section").classList.remove("hidden");
document.getElementById("filter-section").classList.add("flex");
// Set default sort to Start Address Ascending
sortBy("start");
}
function updateStats(regions) {
const container = document.getElementById("stats");
const totalRegions = regions.length;
const totalDirty = regions.reduce((acc, r) => acc + r.dirtyCount, 0);
// BigInt total size summation
let totalSizeBytes = 0n;
regions.forEach((r) => (totalSizeBytes += r.size));
const execCount = regions.filter((r) => r.perms.includes("x")).length;
const writeCount = regions.filter((r) => r.perms.includes("w")).length;
const createCard = (label, value, colorClass) => `
`;
container.innerHTML =
createCard(
"Total Regions",
totalRegions,
"text-slate-800 dark:text-slate-200",
) +
createCard(
"Total Size",
formatBytes(totalSizeBytes),
"text-indigo-600 dark:text-indigo-400",
) +
createCard("Executable", execCount, "text-red-500 dark:text-red-400") +
createCard("Writable", writeCount, "text-amber-500 dark:text-amber-400") +
createCard(
"Dirty Pages",
totalDirty,
"text-emerald-600 dark:text-emerald-400",
);
}
function renderTable(regions) {
const tbody = document.getElementById("table-body");
const footer = document.getElementById("footer-count");
if (regions.length === 0) {
tbody.innerHTML = `| No matching regions found. |
`;
footer.textContent = "0 rows";
return;
}
// Limit rendering for performance if massive (simple virtualization cap)
const renderLimit = 2000;
const dataset = regions.slice(0, renderLimit);
const html = dataset
.map((r) => {
const isExecutable = r.perms.includes("x");
const isWritable = r.perms.includes("w");
const isReadable = r.perms.includes("r");
// Badges for perms
let permBadges = "";
if (isReadable)
permBadges += `R`;
else
permBadges += `-`;
if (isWritable)
permBadges += `W`;
else
permBadges += `-`;
if (isExecutable)
permBadges += `X`;
else
permBadges += `-`;
// Row highlighting
const rowClass =
r.dirtyCount > 0
? "bg-orange-50 dark:bg-orange-900/20 hover:bg-orange-100 dark:hover:bg-orange-900/30"
: "hover:bg-slate-50 dark:hover:bg-slate-700";
return `
| ${r.startStr} |
${r.endStr} |
${formatBytes(r.size)} |
${permBadges} |
${r.name || 'unnamed'} |
${
r.dirtyCount > 0
? `${r.dirtyCount}`
: '-'
}
|
`;
})
.join("");
tbody.innerHTML = html;
const remaining = regions.length - renderLimit;
footer.innerHTML =
remaining > 0
? `${dataset.length} rows shown (${remaining} hidden for performance)`
: `${dataset.length} rows`;
}
// --- Helpers & Utilities ---
function formatBytes(bigIntBytes) {
if (bigIntBytes === 0n) return "0 B";
// Convert to Number for display formatting (precision loss at PB scale is acceptable for UI)
const bytes = Number(bigIntBytes);
const k = 1024;
const sizes = ["B", "KB", "MB", "GB", "TB", "PB"];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
}
function sortBy(column) {
if (currentSort.column === column) {
currentSort.direction = currentSort.direction === "asc" ? "desc" : "asc";
} else {
currentSort.column = column;
currentSort.direction = "asc";
}
// Visual indicator helper
document
.querySelectorAll(".sort-icon")
.forEach((el) => (el.textContent = "⇅"));
document
.querySelectorAll(".sort-icon")
.forEach((el) => el.classList.remove("text-indigo-600"));
// Update active header
const headers = document.querySelectorAll("th");
headers.forEach((th) => {
if (th.onclick.toString().includes(column)) {
const icon = th.querySelector(".sort-icon");
icon.textContent = currentSort.direction === "asc" ? "▲" : "▼";
icon.classList.add("text-indigo-600");
icon.classList.remove("invisible");
}
});
applyFilters();
}
function applyFilters() {
const addrFilter = document
.getElementById("addressFilter")
.value.toLowerCase();
const permsFilter = document.getElementById("permsFilter").value;
const dirtyFilter = document.getElementById("dirtyFilter").value;
const hideEmpty = document.getElementById("hideEmptyRegions").checked;
let filtered = allRegions.filter((r) => {
// 1. Empty/Permissions check
if (hideEmpty && r.perms.startsWith("---")) return false;
// 2. Address/Name Search
if (addrFilter) {
const searchStr = (r.startStr + r.endStr + r.name).toLowerCase();
if (!searchStr.includes(addrFilter)) return false;
}
// 3. Permissions Dropdown
if (permsFilter) {
if (
permsFilter === "rw" &&
(!r.perms.includes("r") || !r.perms.includes("w"))
)
return false;
else if (
permsFilter === "rx" &&
(!r.perms.includes("r") || !r.perms.includes("x"))
)
return false;
else if (permsFilter.length === 1 && !r.perms.includes(permsFilter))
return false;
}
// 4. Dirty Pages
if (dirtyFilter === "dirty" && r.dirtyCount === 0) return false;
if (dirtyFilter === "clean" && r.dirtyCount > 0) return false;
return true;
});
// Apply Sort
filtered.sort((a, b) => {
let valA, valB;
switch (currentSort.column) {
case "start":
valA = a.startBig;
valB = b.startBig;
break;
case "end":
valA = a.endBig;
valB = b.endBig;
break;
case "size":
valA = a.size;
valB = b.size;
break;
case "perms":
valA = a.perms;
valB = b.perms;
break; // string comparison
case "name":
valA = a.name.toLowerCase();
valB = b.name.toLowerCase();
break;
case "dirty":
valA = a.dirtyCount;
valB = b.dirtyCount;
break;
}
if (valA < valB) return currentSort.direction === "asc" ? -1 : 1;
if (valA > valB) return currentSort.direction === "asc" ? 1 : -1;
return 0;
});
renderTable(filtered);
}
function clearAll() {
document.getElementById("input").value = "";
document.getElementById("table-body").innerHTML =
`| Waiting for input... |
`;
document.getElementById("stats").classList.add("hidden");
document.getElementById("stats").classList.remove("grid");
document.getElementById("filter-section").classList.add("hidden");
document.getElementById("filter-section").classList.remove("flex");
document.getElementById("footer-count").innerText = "0 rows";
// Reset filters
document.getElementById("addressFilter").value = "";
document.getElementById("permsFilter").value = "";
document.getElementById("dirtyFilter").value = "";
allRegions = [];
currentSort = { column: null, direction: "asc" };
}