From c9313e5dbe092e47825f4b8a65aa10a051e235f4 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Fri, 12 Nov 2021 00:47:12 +0100 Subject: [PATCH] Add support for ld65 label file format (#128) --- .gitignore | 1 + docs/api/command-line.md | 2 + docs/api/custom-platform.md | 2 + .../scala/millfork/DebugOutputFormat.scala | 52 ++++++++++++++++++- .../millfork/output/BankLayoutInFile.scala | 6 ++- 5 files changed, 61 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 018de792..05d069aa 100644 --- a/.gitignore +++ b/.gitignore @@ -36,6 +36,7 @@ issue*.mfk *.fns *.sym *.mlb +*.dbg *.deb *.xex *.nes diff --git a/docs/api/command-line.md b/docs/api/command-line.md index 75526096..a360d685 100644 --- a/docs/api/command-line.md +++ b/docs/api/command-line.md @@ -50,6 +50,8 @@ The extension and the file format are platform-dependent. * `-G mesen` – format used by the Mesen emulator. The extension is `.mlb`. + * `-G ld65` – a simplified version of the format used by the `ld65` linker (used by CC65 and CA65). The extension is `.dbg`. + * `-G raw` – Millfork-specific format. The extension is '.labels'. Each row contains bank number, start address, end address (if known), object type, and Millfork-specific object identifier. * `-fbreakpoints`, `-fno-breakpoints` – diff --git a/docs/api/custom-platform.md b/docs/api/custom-platform.md index eb3344f5..3cece8c6 100644 --- a/docs/api/custom-platform.md +++ b/docs/api/custom-platform.md @@ -269,3 +269,5 @@ Default: `main,*` * `fceux` – multi-file format used by the FCEUX emulator. The extension is `.nl`. * `mesen` – format used by the Mesen emulator. The extension is `.mlb`. + + * `ld65` – format used by the `ld65` linker. The extension is `.dbg`. diff --git a/src/main/scala/millfork/DebugOutputFormat.scala b/src/main/scala/millfork/DebugOutputFormat.scala index 8381f4cc..f942938a 100644 --- a/src/main/scala/millfork/DebugOutputFormat.scala +++ b/src/main/scala/millfork/DebugOutputFormat.scala @@ -3,6 +3,7 @@ package millfork import millfork.output.{BankLayoutInFile, FormattableLabel} import java.util.regex.Pattern +import scala.collection.mutable import scala.util.control.Breaks.{break, breakable} /** @@ -20,6 +21,10 @@ object DebugOutputFormat { "mlb" -> MesenOutputFormat, "mesen" -> MesenOutputFormat, "asm6f" -> MesenOutputFormat, + "ld65" -> Ld65OutputFormat, + "ca65" -> Ld65OutputFormat, + "cc65" -> Ld65OutputFormat, + "dbg" -> Ld65OutputFormat, "sym" -> SymDebugOutputFormat) } @@ -175,4 +180,49 @@ object MesenOutputFormat extends DebugOutputFormat { override def filePerBank: Boolean = false override def addOutputExtension: Boolean = false -} \ No newline at end of file +} + +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") + } +} diff --git a/src/main/scala/millfork/output/BankLayoutInFile.scala b/src/main/scala/millfork/output/BankLayoutInFile.scala index 8535ad3d..9b6034d9 100644 --- a/src/main/scala/millfork/output/BankLayoutInFile.scala +++ b/src/main/scala/millfork/output/BankLayoutInFile.scala @@ -28,5 +28,9 @@ class BankLayoutInFile(startInFile: Map[String, Int], firstAddress: Map[String, def wasWritten(bank: String): Boolean = startInFile.contains(bank) - def Ld65Offset(bank: String): Int = startInFile(bank) + def getStart(bank:String): Int = firstAddress.getOrElse(bank, 0) // ?? + + def ld65Offset(bank: String): Int = startInFile(bank) + + def allBanks(): Set[String] = firstAddress.keySet }