mirror of
https://github.com/mcanlas/6502-opcodes.git
synced 2024-09-27 06:54:33 +00:00
142 lines
4.0 KiB
Scala
142 lines
4.0 KiB
Scala
package com.htmlism.firepower.core
|
|
|
|
import cats.syntax.all._
|
|
|
|
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:
|
|
case class Instruction(code: String, operand: Option[String], comment: Option[String]):
|
|
def length: Int =
|
|
(code + " " + operand.getOrElse("")).length
|
|
|
|
object Instruction:
|
|
def one(code: String, operand: String, comment: Option[String]): Instruction =
|
|
Instruction(code, operand.some, comment)
|
|
|
|
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 { i =>
|
|
val leftSlug =
|
|
instruction(i.code, opts) + i.operand.map(" " + _).getOrElse("")
|
|
|
|
i.comment 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
|