2019-06-14 09:39:11 +00:00
|
|
|
package millfork
|
|
|
|
|
2021-11-03 20:48:45 +00:00
|
|
|
import millfork.output.{BankLayoutInFile, FormattableLabel}
|
|
|
|
|
|
|
|
import java.util.regex.Pattern
|
2021-11-11 23:47:12 +00:00
|
|
|
import scala.collection.mutable
|
2021-11-03 20:48:45 +00:00
|
|
|
import scala.util.control.Breaks.{break, breakable}
|
|
|
|
|
2019-06-14 09:39:11 +00:00
|
|
|
/**
|
|
|
|
* @author Karol Stasiak
|
|
|
|
*/
|
|
|
|
|
|
|
|
object DebugOutputFormat {
|
|
|
|
val map: Map[String, DebugOutputFormat] = Map(
|
2021-09-20 22:09:59 +00:00
|
|
|
"raw" -> RawDebugOutputFormat,
|
2019-06-14 09:39:11 +00:00
|
|
|
"vice" -> ViceDebugOutputFormat,
|
|
|
|
"nesasm" -> NesasmDebugOutputFormat,
|
|
|
|
"fns" -> NesasmDebugOutputFormat,
|
|
|
|
"fceux" -> FceuxDebugOutputFormat,
|
|
|
|
"nl" -> FceuxDebugOutputFormat,
|
2021-11-03 20:48:45 +00:00
|
|
|
"mlb" -> MesenOutputFormat,
|
|
|
|
"mesen" -> MesenOutputFormat,
|
|
|
|
"asm6f" -> MesenOutputFormat,
|
2021-11-11 23:47:12 +00:00
|
|
|
"ld65" -> Ld65OutputFormat,
|
|
|
|
"ca65" -> Ld65OutputFormat,
|
|
|
|
"cc65" -> Ld65OutputFormat,
|
|
|
|
"dbg" -> Ld65OutputFormat,
|
2019-06-14 09:39:11 +00:00
|
|
|
"sym" -> SymDebugOutputFormat)
|
|
|
|
}
|
|
|
|
|
2020-03-15 22:48:27 +00:00
|
|
|
sealed trait DebugOutputFormat {
|
|
|
|
|
2021-11-03 20:48:45 +00:00
|
|
|
def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
|
|
|
|
val labelPart = labelsHeader + labels.map(formatLine).mkString("\n") + "\n"
|
2020-03-15 22:48:27 +00:00
|
|
|
if (breakpoints.isEmpty) {
|
|
|
|
labelPart
|
|
|
|
} else {
|
|
|
|
labelPart + breakpointsHeader + breakpoints.flatMap(formatBreakpointTupled).mkString("\n") + "\n"
|
|
|
|
}
|
|
|
|
}
|
2019-06-14 09:39:11 +00:00
|
|
|
|
2021-11-03 20:48:45 +00:00
|
|
|
def formatLine(label: FormattableLabel): String
|
2019-06-14 09:39:11 +00:00
|
|
|
|
2020-03-15 22:48:27 +00:00
|
|
|
final def formatBreakpointTupled(value: (Int, Int)): Seq[String] = formatBreakpoint(value._1, value._2).toSeq
|
|
|
|
|
|
|
|
def formatBreakpoint(bank: Int, value: Int): Option[String]
|
|
|
|
|
2019-06-14 09:39:11 +00:00
|
|
|
def fileExtension(bank: Int): String
|
|
|
|
|
|
|
|
def filePerBank: Boolean
|
|
|
|
|
2020-03-15 22:48:27 +00:00
|
|
|
//noinspection MutatorLikeMethodIsParameterless
|
2019-06-14 09:39:11 +00:00
|
|
|
def addOutputExtension: Boolean
|
2020-03-15 22:48:27 +00:00
|
|
|
|
|
|
|
def labelsHeader: String = ""
|
|
|
|
|
|
|
|
def breakpointsHeader: String = ""
|
2019-06-14 09:39:11 +00:00
|
|
|
}
|
|
|
|
|
2021-09-20 22:09:59 +00:00
|
|
|
object RawDebugOutputFormat extends DebugOutputFormat {
|
2021-11-03 20:48:45 +00:00
|
|
|
override def formatLine(label: FormattableLabel): String = {
|
|
|
|
f"${label.bankNumber}%02X:${label.startValue}%04X:${label.endValue.fold("")(_.formatted("%04X"))}%s:${label.category}%s:$label%s"
|
2021-09-20 22:09:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override def fileExtension(bank: Int): String = ".labels"
|
|
|
|
|
|
|
|
override def filePerBank: Boolean = false
|
|
|
|
|
|
|
|
override def addOutputExtension: Boolean = false
|
|
|
|
|
|
|
|
override def formatBreakpoint(bank: Int, value: Int): Option[String] =
|
|
|
|
Some(f"$bank%02X:$value%04X::b:<breakpoint@$value%04X>")
|
|
|
|
}
|
|
|
|
|
2019-06-14 09:39:11 +00:00
|
|
|
object ViceDebugOutputFormat extends DebugOutputFormat {
|
2021-11-03 20:48:45 +00:00
|
|
|
override def formatLine(label: FormattableLabel): String = {
|
|
|
|
val normalized = label.labelName.replace('$', '_').replace('.', '_')
|
|
|
|
s"al ${label.startValue.toHexString} .$normalized"
|
2019-06-14 09:39:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override def fileExtension(bank: Int): String = ".lbl"
|
|
|
|
|
|
|
|
override def filePerBank: Boolean = false
|
|
|
|
|
|
|
|
override def addOutputExtension: Boolean = false
|
2020-03-15 22:48:27 +00:00
|
|
|
|
|
|
|
override def formatBreakpoint(bank: Int, value: Int): Option[String] = Some(s"break ${value.toHexString}")
|
2019-06-14 09:39:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
object NesasmDebugOutputFormat extends DebugOutputFormat {
|
2021-11-03 20:48:45 +00:00
|
|
|
override def formatLine(label: FormattableLabel): String = {
|
|
|
|
label.labelName + " = $" + label.startValue.toHexString
|
2019-06-14 09:39:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override def fileExtension(bank: Int): String = ".fns"
|
|
|
|
|
|
|
|
override def filePerBank: Boolean = false
|
|
|
|
|
|
|
|
override def addOutputExtension: Boolean = false
|
2020-03-15 22:48:27 +00:00
|
|
|
|
|
|
|
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
|
2019-06-14 09:39:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
object SymDebugOutputFormat extends DebugOutputFormat {
|
2021-11-03 20:48:45 +00:00
|
|
|
override def formatLine(label: FormattableLabel): String = {
|
|
|
|
f"${label.bankNumber}%02x:${label.startValue}%04x ${label.labelName}%s"
|
2019-06-14 09:39:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override def fileExtension(bank: Int): String = ".sym"
|
|
|
|
|
|
|
|
override def filePerBank: Boolean = false
|
|
|
|
|
|
|
|
override def addOutputExtension: Boolean = false
|
2020-03-15 22:48:27 +00:00
|
|
|
|
|
|
|
override def formatBreakpoint(bank: Int, value: Int): Option[String] = Some(f"$bank%02x:$value%04x")
|
|
|
|
|
|
|
|
override def labelsHeader: String = "[labels]\n"
|
|
|
|
|
|
|
|
override def breakpointsHeader: String = "[breakpoints]\n"
|
2019-06-14 09:39:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
object FceuxDebugOutputFormat extends DebugOutputFormat {
|
2021-11-03 20:48:45 +00:00
|
|
|
override def formatLine(label: FormattableLabel): String = {
|
|
|
|
f"$$${label.startValue}%04x#${label.labelName}%s#"
|
2019-06-14 09:39:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
override def fileExtension(bank: Int): String = if (bank == 0xff) ".ram.nl" else s".$bank.nl"
|
|
|
|
|
|
|
|
override def filePerBank: Boolean = true
|
|
|
|
|
|
|
|
override def addOutputExtension: Boolean = true
|
2020-03-15 22:48:27 +00:00
|
|
|
|
|
|
|
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
|
2019-06-14 09:39:11 +00:00
|
|
|
}
|
2021-11-03 20:48:45 +00:00
|
|
|
|
|
|
|
object MesenOutputFormat extends DebugOutputFormat {
|
|
|
|
|
|
|
|
override def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
|
|
|
|
val allStarts = labels.groupBy(_.bankName).mapValues(_.map(_.startValue).toSet)
|
|
|
|
labels.flatMap{ l =>
|
|
|
|
val mesenShift = b.getMesenShift(l.bankName)
|
|
|
|
val shiftedStart = l.startValue + mesenShift
|
|
|
|
var shiftedEnd = l.endValue.map(_ + mesenShift).filter(_ != shiftedStart)
|
|
|
|
l.endValue match {
|
|
|
|
case None =>
|
|
|
|
case Some(e) =>
|
|
|
|
// Mesen does not like labels of form XXX-XXX, where both ends are equal
|
|
|
|
breakable {
|
|
|
|
for (i <- l.startValue.+(1) to e) {
|
|
|
|
if (allStarts.getOrElse(l.bankName, Set.empty).contains(i)) {
|
|
|
|
shiftedEnd = None
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (shiftedStart >= 0 && shiftedEnd.forall(_ >= 0)) {
|
|
|
|
val normalized = l.labelName.replace('$', '_').replace('.', '_')
|
|
|
|
val comment = (l.category match {
|
|
|
|
case 'F' => "function "
|
|
|
|
case 'A' => "initialized array "
|
|
|
|
case 'V' => "initialized variable "
|
|
|
|
case 'a' => "array "
|
|
|
|
case 'v' => "variable "
|
|
|
|
case _ => ""
|
|
|
|
}) + l.labelName
|
|
|
|
Some(f"${l.mesenSymbol}%s:${shiftedStart}%04X${shiftedEnd.fold("")(e => f"-$e%04X")}%s:$normalized%s:$comment%s")
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}.mkString("\n")
|
|
|
|
}
|
|
|
|
|
|
|
|
override def formatLine(label: FormattableLabel): String = throw new UnsupportedOperationException()
|
|
|
|
|
|
|
|
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
|
|
|
|
|
|
|
|
override def fileExtension(bank: Int): String = ".mlb"
|
|
|
|
|
|
|
|
override def filePerBank: Boolean = false
|
|
|
|
|
|
|
|
override def addOutputExtension: Boolean = false
|
2021-11-11 23:47:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
object Ld65OutputFormat extends DebugOutputFormat {
|
|
|
|
override def formatLine(label: FormattableLabel): String = throw new UnsupportedOperationException()
|
|
|
|
|
|
|
|
override def formatBreakpoint(bank: Int, value: Int): Option[String] = None
|
|
|
|
|
|
|
|
override def fileExtension(bank: Int): String = ".dbg"
|
|
|
|
|
|
|
|
override def filePerBank: Boolean = false
|
|
|
|
|
|
|
|
override def addOutputExtension: Boolean = true
|
|
|
|
|
|
|
|
override def formatAll(b: BankLayoutInFile, labels: Seq[FormattableLabel], breakpoints: Seq[(Int, Int)]): String = {
|
|
|
|
val q = '"'
|
|
|
|
val allBanksInFile = b.allBanks()
|
|
|
|
val allBanks = (labels.map(_.bankName).distinct ++ allBanksInFile).distinct
|
|
|
|
val result = mutable.ListBuffer[String]()
|
|
|
|
result += "version\tmajor=2,minor=0"
|
|
|
|
result += s"info\tcsym=0,file=0,lib=0,line=0,mod=0,scope=1,seg=${allBanks.size},span=0,sym=${labels.size},type=4"
|
|
|
|
for ((bank, ix) <- allBanks.zipWithIndex) {
|
|
|
|
val common = s"seg\tid=${ix},name=$q${bank}$q,start=0x${b.getStart(bank).toHexString},size=0x0,addrsize=absolute"
|
|
|
|
if (allBanksInFile.contains(bank)) {
|
|
|
|
result += common + s",ooffs=${b.ld65Offset(bank)}"
|
|
|
|
} else {
|
|
|
|
result += common
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result += "scope\tid=0,name=\"\""
|
|
|
|
for ((label, ix) <- labels.sortBy(l => (l.bankName, l.startValue, l.labelName)).zipWithIndex) {
|
|
|
|
val name = label.labelName.replace('$', '@').replace('.', '@')
|
|
|
|
val sb = new StringBuilder
|
|
|
|
sb ++= s"sym\tid=${ix},name=$q${name}$q,addrsize=absolute,"
|
|
|
|
label.endValue match {
|
|
|
|
case Some(e) =>
|
|
|
|
if (!labels.exists(l => l.ne(label) && l.startValue >= label.startValue && l.startValue <= e)) {
|
|
|
|
sb ++= s"size=${ e - label.startValue + 1 },"
|
|
|
|
}
|
|
|
|
case None =>
|
|
|
|
}
|
|
|
|
sb ++= s"scope=0,val=0x${label.startValue.toHexString},seg=${allBanks.indexOf(label.bankName)},type=lab"
|
|
|
|
result += sb.toString
|
|
|
|
}
|
|
|
|
result.mkString("\n")
|
|
|
|
}
|
|
|
|
}
|