1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-06-02 00:41:40 +00:00
millfork/src/main/scala/millfork/parser/MosParser.scala
Karol Stasiak 63ff28e94e Changes to macros and parameter list syntax:
* non-asm macros can now take `const` and `call` parameters
* register parameters to asm functions and macros can be given names if annotated explicitly
2020-03-30 19:23:48 +02:00

163 lines
8.3 KiB
Scala

package millfork.parser
import fastparse.all._
import millfork.assembly.mos.{AddrMode, AssemblyLine, Opcode, OpcodeClasses}
import millfork.env._
import millfork.node._
import millfork.{CompilationFlag, CompilationOptions}
import millfork.assembly.Elidability
import millfork.output.{MemoryAlignment, WithinPageAlignment}
/**
* @author Karol Stasiak
*/
case class MosParser(filename: String, input: String, currentDirectory: String, options: CompilationOptions, featureConstants: Map[String, Long]) extends MfParser[AssemblyLine](filename, input, currentDirectory, options, featureConstants) {
import MfParser._
def allowIntelHexAtomsInAssembly: Boolean = false
def fastAlignmentForArrays: MemoryAlignment = WithinPageAlignment
def fastAlignmentForFunctions: MemoryAlignment = WithinPageAlignment
// TODO: label and instruction in one line
val asmLabel: P[ExecutableStatement] = (identifier ~ HWS ~ ":" ~/ HWS).map(l => MosAssemblyStatement(Opcode.LABEL, AddrMode.DoesNotExist, VariableExpression(l), Elidability.Elidable))
// def zeropageAddrModeHint: P[Option[Boolean]] = Pass
val asmOpcode: P[Opcode.Value] = (position() ~ mosOpcodeLetter.rep(exactly = 3).! ~ octalDigit.?.! ~ ("_W" | "_w").?.!).map { case (p, bitNo, suffix, o) => Opcode.lookup(o + bitNo + suffix, Some(p), log) }
private val commaX = HWS ~ "," ~ HWS ~ ("X" | "x") ~ HWS
private val commaY = HWS ~ "," ~ HWS ~ ("Y" | "y") ~ HWS
private val commaZ = HWS ~ "," ~ HWS ~ ("Z" | "z") ~ HWS
private val commaS = HWS ~ "," ~ HWS ~ ("S" | "s") ~ HWS
val farKeyword: P[Unit] = P(("f" | "F") ~ ("a" | "A") ~ ("r" | "R"))
def isObviouslyZeropage(e: Expression): Boolean = e match {
case LiteralExpression(value, size) => size == 1 && value >= 0 && value <= 0xff
case FunctionCallExpression("lo" | "hi", _) => true
case FunctionCallExpression("|" | "&" | "^", exprs) => exprs.forall(isObviouslyZeropage)
case _ => false
}
val asmParameter: P[(AddrMode.Value, Expression)] = {
(SWS ~ (
("##" ~ asmExpression).map(AddrMode.WordImmediate -> _) |
("#" ~ asmExpression).map(AddrMode.Immediate -> _) |
("(" ~ HWS ~ asmExpression ~ HWS ~ ")" ~ commaY).map(AddrMode.IndexedY -> _) |
(farKeyword ~ HWS ~ "(" ~ HWS ~ asmExpression ~ HWS ~ ")" ~ commaY).map(AddrMode.LongIndexedY -> _) |
("(" ~ HWS ~ asmExpression ~ commaS ~ ")" ~ commaY).map(AddrMode.IndexedSY -> _) |
("(" ~ HWS ~ asmExpression ~ HWS ~ ")" ~ commaZ).map(AddrMode.IndexedZ -> _) |
("(" ~ HWS ~ asmExpression ~ commaX ~ ")").map(AddrMode.IndexedX -> _) |
("(" ~ HWS ~ asmExpression ~ HWS ~ ")").map(AddrMode.Indirect -> _) |
(farKeyword ~ HWS ~ "(" ~ HWS ~ asmExpression ~ HWS ~ ")").map(AddrMode.LongIndexedZ -> _) |
(farKeyword ~ HWS ~ asmExpression ~ commaX).map(AddrMode.LongAbsoluteX -> _) |
(farKeyword ~ HWS ~ asmExpression).map(AddrMode.LongAbsolute -> _) |
(asmExpression ~ commaS).map(AddrMode.Stack -> _) |
(asmExpression ~ commaX).map { param =>
if (isObviouslyZeropage(param)) {
AddrMode.ZeroPageX -> param
} else {
AddrMode.AbsoluteX -> param
}
} |
(asmExpression ~ commaY).map { param =>
if (isObviouslyZeropage(param)) {
AddrMode.ZeroPageY -> param
} else {
AddrMode.AbsoluteY -> param
}
} |
// automatic zero page is handled elsewhere
asmExpression.map(AddrMode.Absolute -> _)
)).?.map(_.getOrElse(AddrMode.Implied -> LiteralExpression(0, 1)))
}
val asmInstruction: P[ExecutableStatement] = {
import Opcode._
for {
elid <- !"}" ~ elidable
position <- position("assembly statement")
op <- asmOpcode ~/ Pass
param <- op match {
case op if OpcodeClasses.SingleBitBranch(op) =>
(HWS ~ asmExpression ~ HWS ~ "," ~/ HWS ~ asmExpression).map{ x =>
AddrMode.ZeroPageWithRelative -> FunctionCallExpression("byte_and_pointer$", List(x._1, x._2))
}
case op if OpcodeClasses.HudsonTransfer(op) =>
(HWS ~ asmExpression ~ HWS ~ "," ~/ HWS ~ asmExpression ~ HWS ~ "," ~/ HWS ~ asmExpression).map{ x =>
AddrMode.TripleAbsolute -> FunctionCallExpression("hudson_transfer$", List(x._1, x._2, x._3))
}
case Opcode.TST => (HWS ~ "#" ~/ HWS ~ asmExpression ~ HWS ~ "," ~/ HWS ~ asmExpression ~ commaX.!.?).map { x =>
(if (x._3.isDefined) AddrMode.ImmediateWithAbsoluteX else AddrMode.ImmediateWithAbsolute )-> FunctionCallExpression("byte_and_pointer$", List(x._1, x._2))
}
case _ => asmParameter
}
} yield {
((op, param._1) match {
case (Opcode.SAX, AddrMode.Implied) => MosAssemblyStatement(Opcode.HuSAX, param._1, param._2, elid)
case (Opcode.SBX, AddrMode.Immediate) => MosAssemblyStatement(Opcode.SBX, param._1, param._2, elid)
case (Opcode.SAY, AddrMode.AbsoluteX) => MosAssemblyStatement(Opcode.SHY, param._1, param._2, elid)
case (Opcode.SAX, AddrMode.AbsoluteY) => MosAssemblyStatement(Opcode.SAX, AddrMode.ZeroPageY, param._2, elid)
case (Opcode.ASR, AddrMode.Absolute) => MosAssemblyStatement(Opcode.ASR, AddrMode.ZeroPage, param._2, elid)
case (Opcode.ASR, AddrMode.AbsoluteX) => MosAssemblyStatement(Opcode.ASR, AddrMode.ZeroPageX, param._2, elid)
case (Opcode.SBX, _) => MosAssemblyStatement(Opcode.SAX, param._1, param._2, elid)
case (_, AddrMode.ZeroPageX) if !OpcodeClasses.SupportsZeroPageX(op) => MosAssemblyStatement(op, AddrMode.AbsoluteX, param._2, elid)
case (_, AddrMode.ZeroPageY) if !OpcodeClasses.SupportsZeroPageY(op) => MosAssemblyStatement(op, AddrMode.AbsoluteY, param._2, elid)
case (_, AddrMode.Absolute) if OpcodeClasses.SingleBit(op) => MosAssemblyStatement(op, AddrMode.ZeroPage, param._2, elid)
case (_, AddrMode.Indirect) if op != Opcode.JMP && op != Opcode.JSR => MosAssemblyStatement(op, AddrMode.IndexedZ, param._2, elid)
case _ => MosAssemblyStatement(op, param._1, param._2, elid)
}).pos(position)
}
}
val asmMacro: P[ExecutableStatement] = ("+" ~/ HWS ~/ functionCall(false)).map(ExpressionStatement)
val asmStatement: P[ExecutableStatement] = (position("assembly statement") ~ P(asmLabel | asmMacro | arrayContentsForAsm | asmInstruction)).map { case (p, s) => s.pos(p) } // TODO: macros
override val appcRegister: P[ParamPassingConvention] = P(("xy" | "yx" | "ax" | "ay" | "xa" | "ya" | "a" | "x" | "y") ~ !letterOrDigit).!.map {
case "xy" => ByMosRegister(MosRegister.XY)
case "yx" => ByMosRegister(MosRegister.YX)
case "ax" => ByMosRegister(MosRegister.AX)
case "ay" => ByMosRegister(MosRegister.AY)
case "xa" => ByMosRegister(MosRegister.XA)
case "ya" => ByMosRegister(MosRegister.YA)
case "a" => ByMosRegister(MosRegister.A)
case "x" => ByMosRegister(MosRegister.X)
case "y" => ByMosRegister(MosRegister.Y)
case x => log.fatal(s"Unknown assembly parameter passing convention: `$x`")
}
def validateAsmFunctionBody(p: Position, flags: Set[String], name: String, statements: Option[List[Statement]]): Unit = {
if (!options.flag(CompilationFlag.BuggyCodeWarning)) return
statements match {
case Some(Nil) => log.warn("Assembly function `$name` is empty, did you mean RTS, RTI or JMP", Some(p))
case Some(xs) =>
if (flags("interrupt")) {
if (xs.exists {
case MosAssemblyStatement(Opcode.RTS, _, _, _) => true
case _ => false
}) log.warn("Assembly interrupt function `$name` contains RTS, did you mean RTI?", Some(p))
} else {
if (xs.exists {
case MosAssemblyStatement(Opcode.RTI, _, _, _) => true
case _ => false
}) log.warn("Assembly non-interrupt function `$name` contains RTI, did you mean RTS?", Some(p))
}
if (!name.startsWith("__") && !flags("macro")) {
xs.last match {
case MosAssemblyStatement(Opcode.RTS, _, _, _) => () // OK
case MosAssemblyStatement(Opcode.RTI, _, _, _) => () // OK
case MosAssemblyStatement(Opcode.JMP, _, _, _) => () // OK
case _ =>
val validReturn = if (flags("interrupt")) "RTI" else "RTS"
log.warn(s"Non-macro assembly function `$name` should end in " + validReturn, Some(p))
}
}
case None => ()
}
}
}