mirror of
https://github.com/mcanlas/6502-opcodes.git
synced 2024-06-26 01:29:31 +00:00
155 lines
4.4 KiB
Scala
155 lines
4.4 KiB
Scala
package com.htmlism.firepower.core
|
|
|
|
sealed trait AsmBlock
|
|
|
|
object AsmBlock:
|
|
/**
|
|
* Lines of text to render as a comment block
|
|
*/
|
|
case class CommentBlock(xs: List[String]) extends AsmBlock
|
|
|
|
// TODO factory method for automatic line wrapping given some width
|
|
object CommentBlock:
|
|
/**
|
|
* Factory method for multi-line ASCII art
|
|
*/
|
|
def fromMultiline(s: String): CommentBlock =
|
|
CommentBlock(s.split("\\n").toList)
|
|
|
|
/**
|
|
* Renders as a table of aliases
|
|
*/
|
|
// TODO needs descriptive field
|
|
case class DefinesBlock(xs: List[(String, Int)]) extends AsmBlock
|
|
|
|
/**
|
|
* Renders as a labeled subroutine
|
|
*/
|
|
case class NamedCodeBlock(name: String, comment: Option[String], intents: List[AsmBlock.Intent]) extends AsmBlock
|
|
|
|
/**
|
|
* Renders as an unlabeled block of code
|
|
*/
|
|
case class AnonymousCodeBlock(intents: List[AsmBlock.Intent]) extends AsmBlock
|
|
|
|
/**
|
|
* Like a fancy `mkString` at the `F` level. Defers the projection of `A => F[B]` so that a container type isn't
|
|
* needed (in the cases where `A` may yield a different length of `F[_]` than the interlaced payload)
|
|
*
|
|
* Not known to exist in `cats` given that it needs both `Semigroup` and an awareness of head/tail/emptiness
|
|
*/
|
|
def interFlatMap[A, B](xs: List[A])(x: List[B], f: A => List[B]): List[B] =
|
|
xs match
|
|
case head :: tail =>
|
|
f(head) ::: tail.flatMap(a => x ::: f(a))
|
|
|
|
case Nil =>
|
|
Nil
|
|
|
|
def toComment(s: String): String =
|
|
"; " + s
|
|
|
|
def withIndent(s: String): String =
|
|
" " + s
|
|
|
|
def toHex(n: Int): String =
|
|
val hex =
|
|
if (n < 16 * 16)
|
|
String.format("%1$02x", n)
|
|
else if (n < 16 * 16 * 16)
|
|
String.format("%1$03x", n)
|
|
else
|
|
String.format("%1$04x", n)
|
|
|
|
"$" + hex.toUpperCase
|
|
|
|
def toLines(opts: AssemblerOptions.InstructionCase)(xs: AsmBlock): List[String] =
|
|
xs match
|
|
case CommentBlock(ys) =>
|
|
ys.map(toComment)
|
|
|
|
case DefinesBlock(kvs) =>
|
|
val maximumLength =
|
|
kvs
|
|
.map(_._1.length)
|
|
.max
|
|
|
|
kvs
|
|
.map { case (k, v) =>
|
|
String.format(s"define %-${maximumLength}s ${toHex(v)}", k)
|
|
}
|
|
|
|
case NamedCodeBlock(label, oComment, intents) =>
|
|
val headerParagraph =
|
|
List(label + ":") ++ oComment.map(toComment).map(withIndent).toList
|
|
|
|
val intentParagraphs =
|
|
intents.map(Intent.toLines(opts))
|
|
|
|
interFlatMap(headerParagraph :: intentParagraphs)(List(""), identity)
|
|
|
|
case AnonymousCodeBlock(intents) =>
|
|
interFlatMap(intents)(List(""), Intent.toLines(opts))
|
|
|
|
case class Intent(label: Option[String], instructions: List[Intent.Instruction])
|
|
|
|
object Intent:
|
|
sealed trait Instruction:
|
|
def length: Int
|
|
|
|
object Instruction:
|
|
case class One(code: String, operand: String, comment: Option[String]) extends Instruction:
|
|
def length: Int =
|
|
operand.length + 4
|
|
case class Zero(code: String, comment: Option[String]) extends Instruction:
|
|
def length: Int =
|
|
0
|
|
|
|
def toLines(opts: AssemblerOptions.InstructionCase)(x: Intent): List[String] =
|
|
val comment =
|
|
x.label.map(toComment).map(withIndent).toList
|
|
|
|
val maximumLength =
|
|
x
|
|
.instructions
|
|
.map(_.length)
|
|
.max
|
|
|
|
val instructions =
|
|
x
|
|
.instructions
|
|
.map {
|
|
case Instruction.Zero(code, oComment) =>
|
|
val codeUpperLower =
|
|
instruction(code, opts)
|
|
|
|
oComment match
|
|
case Some(c) =>
|
|
String.format(s"%-${maximumLength}s", codeUpperLower) + " " + toComment(c)
|
|
|
|
case None =>
|
|
codeUpperLower
|
|
|
|
case Instruction.One(code, operand, oComment) =>
|
|
val leftSlug =
|
|
instruction(code, opts) + " " + operand
|
|
|
|
oComment match
|
|
case Some(c) =>
|
|
String.format(s"%-${maximumLength}s", leftSlug) + " " + toComment(c)
|
|
|
|
case None =>
|
|
leftSlug
|
|
}
|
|
.map(withIndent)
|
|
|
|
comment ++ instructions
|
|
|
|
def instruction(s: String, instructionCase: AssemblerOptions.InstructionCase): String =
|
|
instructionCase match
|
|
case AssemblerOptions.InstructionCase.Uppercase =>
|
|
s.toUpperCase
|
|
|
|
case AssemblerOptions.InstructionCase.Lowercase =>
|
|
s.toLowerCase
|