disbrowser/src/main/java/com/smallhacker/disbrowser/Grid.kt

190 lines
6.1 KiB
Kotlin
Raw Normal View History

2019-01-07 18:19:37 +00:00
package com.smallhacker.disbrowser
import com.smallhacker.disbrowser.asm.*
class Grid {
private val arrowCells = HashMap<Pair<Int, Int>, HtmlNode?>()
private val arrowClasses = HashMap<Pair<Int, Int>, String>()
private var arrowWidth = 0
private val content = HashMap<Pair<Int, Int>, HtmlNode?>()
private val cellClasses = HashMap<Pair<Int, Int>, String>()
2019-01-11 16:35:35 +00:00
private val addresses = HashMap<SnesAddress, Int>()
2019-01-07 18:19:37 +00:00
private val rowClasses = HashMap<Int, String>()
private val rowId = HashMap<Int, String>()
private var height = 0
2019-01-11 16:35:35 +00:00
private var nextAddress: SnesAddress? = null
2019-01-07 18:19:37 +00:00
2019-01-11 16:35:35 +00:00
fun arrow(from: SnesAddress, to: SnesAddress) {
2019-01-07 18:19:37 +00:00
val yStart = addresses[from]
val yEnd = addresses[to]
if (yStart == null || yEnd == null) {
return
}
val y1: Int
val y2: Int
val dir: String
if (yStart > yEnd) {
dir = "up"
y1 = yEnd
y2 = yStart
} else {
dir = "down"
y1 = yStart
y2 = yEnd
}
val x = nextArrowX(y1, y2)
if ((x + 1) > arrowWidth) {
arrowWidth = x + 1
}
arrowClasses[x to y1] = "arrow arrow-$dir-start"
for (y in (y1 + 1)..(y2 - 1)) {
arrowClasses[x to y] = "arrow arrow-$dir-middle"
}
arrowClasses[x to y2] = "arrow arrow-$dir-end"
//arrowCells[x to yStart] = a().addClass("arrow-link").attr("href", "#${to.toSimpleString()}")
arrowCells[x to yEnd] = div().addClass("arrow-head")
}
private fun nextArrowX(y1: Int, y2: Int): Int {
return generateSequence(0) { it + 1 }
.filter { x ->
(y1..y2).asSequence()
.map { y -> arrowClasses[x to y] }
.all { it == null }
}
.first()
}
2019-01-11 03:19:08 +00:00
fun add(ins: CodeUnit, metadata: Metadata, disassembly: Disassembly) {
2019-01-11 16:35:35 +00:00
val insMetadata = ins.address?.let { metadata[it] }
2019-01-11 03:19:08 +00:00
val actualAddress = ins.address
val presentedAddress = ins.presentedAddress
2019-01-07 18:19:37 +00:00
if (nextAddress != null) {
2019-01-11 03:19:08 +00:00
if (presentedAddress != nextAddress) {
2019-01-07 18:19:37 +00:00
addDummy()
}
}
2019-01-11 03:19:08 +00:00
nextAddress = ins.nextPresentedAddress
2019-01-07 18:19:37 +00:00
val y = (height++)
2019-01-11 03:19:08 +00:00
addresses[presentedAddress] = y
2019-01-07 18:19:37 +00:00
add(y, ins.address,
2019-01-11 03:19:08 +00:00
text(actualAddress?.toFormattedString() ?: ""),
2019-01-07 18:19:37 +00:00
text(ins.bytesToString()),
2019-01-11 05:09:12 +00:00
editableField(presentedAddress, "label", insMetadata?.label),
2019-01-07 18:19:37 +00:00
fragment {
2019-01-11 16:35:35 +00:00
opcodeNode(ins)
2019-01-07 18:19:37 +00:00
text(" ")
2019-01-11 03:19:08 +00:00
var operands = ins.printOperands()
2019-01-07 18:19:37 +00:00
val link = ins.linkedState
if (link == null) {
text(operands)
} else {
val local = link.address in disassembly
val url = when {
local -> "#${link.address.toSimpleString()}"
else -> "/${link.address.toSimpleString()}/${link.urlString}"
}
2019-01-11 03:19:08 +00:00
operands = metadata[link.address]?.label ?: operands
2019-01-07 18:19:37 +00:00
a {
text(operands)
}.attr("href", url)
}
},
2019-01-11 03:19:08 +00:00
text(ins.postState?.toString() ?: ""),
2019-01-11 05:09:12 +00:00
editableField(presentedAddress, "comment", insMetadata?.comment)
2019-01-07 18:19:37 +00:00
)
if (ins.opcode.continuation == Continuation.NO) {
rowClasses[y] = "routine-end"
}
}
2019-01-11 16:35:35 +00:00
private fun HtmlArea.opcodeNode(ins: CodeUnit) {
val alt = ins.printAlternativeOpcodeAndSuffix()
if (alt == null) {
text(ins.printOpcodeAndSuffix())
} else {
span {
text(ins.printOpcodeAndSuffix())
}.attr("title", alt).addClass("opcode-info")
}
}
private fun editableField(address: SnesAddress, type: String, value: String?): HtmlNode {
2019-01-11 05:09:12 +00:00
return input(value = value ?: "")
.addClass("field-$type")
.addClass("field-editable")
.attr("data-field", type)
.attr("data-address", address.toSimpleString())
}
2019-01-07 18:19:37 +00:00
private fun addDummy() {
val y = (height++)
2019-01-11 03:19:08 +00:00
add(y, null, null, null, null, text("..."), null, null)
2019-01-07 18:19:37 +00:00
}
2019-01-11 16:35:35 +00:00
private fun add(y: Int, address: SnesAddress?,
2019-01-07 18:19:37 +00:00
cAddress: HtmlNode?,
cBytes: HtmlNode?,
cLabel: HtmlNode?,
cCode: HtmlNode?,
2019-01-11 03:19:08 +00:00
cState: HtmlNode?,
cComment: HtmlNode?
2019-01-07 18:19:37 +00:00
) {
if (address != null) {
rowId[y] = address.toSimpleString()
}
content[0 to y] = cAddress
content[1 to y] = cBytes
content[2 to y] = cLabel
content[3 to y] = cCode
content[4 to y] = cState
2019-01-11 03:19:08 +00:00
content[5 to y] = cComment
2019-01-07 18:19:37 +00:00
}
fun output(): HtmlNode {
2019-01-11 03:19:08 +00:00
val contentMaxX = content.keys.asSequence()
.map { it.first }
.max()
?: -1
2019-01-07 18:19:37 +00:00
return table {
for (y in 0 until height) {
tr {
for (x in 0..3) {
val cssClass = cellClasses[x to y]
td {
content[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
for (x in 0 until arrowWidth) {
val cssClass = arrowClasses[x to y]
td {
arrowCells[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
2019-01-11 03:19:08 +00:00
for (x in 4..contentMaxX) {
2019-01-07 18:19:37 +00:00
val cssClass = cellClasses[x to y]
td {
content[x to y]?.appendTo(parent)
}.addClass(cssClass)
}
}.addClass(rowClasses[y]).attr("id", rowId[y])
}
}
}
}