From e952d89849a4e09bbd04cc51a68dcf6c3126e39f Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Wed, 1 Aug 2018 21:16:20 +0200 Subject: [PATCH] Z80: Intel syntax for output. --- docs/api/command-line.md | 4 + docs/api/custom-platform.md | 2 + docs/api/getting-started.md | 1 + docs/lang/assemblyz80.md | 8 +- .../scala/millfork/CompilationOptions.scala | 7 +- src/main/scala/millfork/Main.scala | 3 + .../scala/millfork/assembly/z80/ZLine.scala | 190 +++++++++++++++++- src/main/scala/millfork/env/Constant.scala | 108 +++++++--- .../millfork/output/AbstractAssembler.scala | 17 +- .../scala/millfork/output/MosAssembler.scala | 2 + .../scala/millfork/output/Z80Assembler.scala | 2 + .../parser/AbstractSourceLoadingQueue.scala | 13 +- .../parser/MosSourceLoadingQueue.scala | 3 +- .../scala/millfork/parser/Preprocessor.scala | 13 +- .../scala/millfork/parser/Z80Parser.scala | 19 +- .../millfork/parser/ZSourceLoadingQueue.scala | 10 +- src/test/scala/millfork/test/emu/EmuRun.scala | 4 +- .../scala/millfork/test/emu/EmuZ80Run.scala | 7 +- .../millfork/test/emu/ShouldNotCompile.scala | 4 +- 19 files changed, 362 insertions(+), 55 deletions(-) diff --git a/docs/api/command-line.md b/docs/api/command-line.md index 9700e3cf..13e2280f 100644 --- a/docs/api/command-line.md +++ b/docs/api/command-line.md @@ -26,6 +26,10 @@ no extension for BBC micro program file, * `-s` – Generate also the assembly output. It is not compatible with any assembler, but it serves purely informational purpose. The file has the same nam as the output file and the extension is `.asm`. +* `-foutput_intel_syntax`, `-foutput_zilog_syntax` – +Choose syntax for assembly output on 8080-like targets. +`.ini` equivalent: `output_intel_syntax`. Default: Intel (true) on Intel 8080, Zilog (false) otherwise. + * `-g` – Generate also the label file. The label file contains labels with their addresses, with duplicates removed. It can be loaded into the monitor of the Vice emulator for debugging purposes. The file has the same name as the output file and the extension is `.lbl`. * `-I ;` – The include directories. The current working directory is also an include directory. Those directories are searched for modules and platform definitions. diff --git a/docs/api/custom-platform.md b/docs/api/custom-platform.md index 0f924e08..225fbbaf 100644 --- a/docs/api/custom-platform.md +++ b/docs/api/custom-platform.md @@ -77,6 +77,8 @@ Default: the same as `encoding`. * `ix_scratch` – allow using the IY register for other purposes, default is `false` * `iy_scratch` – allow using the IY register for other purposes, default is `false` + + * `output_intel_syntax` – use Intel syntax instead of Zilog syntax, default is `true` for Intel 8080 and `false` otherwise #### `[define]` section diff --git a/docs/api/getting-started.md b/docs/api/getting-started.md index 8b5853da..76bfbcff 100644 --- a/docs/api/getting-started.md +++ b/docs/api/getting-started.md @@ -48,6 +48,7 @@ You may be also interested in the following: * `-fipo` – enable interprocedural optimization * `-s` – additionally generate assembly output +(if targeting Intel 8080, use `-foutput_intel_syntax` or `-foutput_zilog_syntax` to choose the preferred output syntax) * `-g` – additionally generate a label file, in format compatible with VICE emulator diff --git a/docs/lang/assemblyz80.md b/docs/lang/assemblyz80.md index 803a4657..88c237b8 100644 --- a/docs/lang/assemblyz80.md +++ b/docs/lang/assemblyz80.md @@ -10,7 +10,11 @@ There are two ways to include raw assembly code in your Millfork programs: ## Assembly syntax -Millfork uses Zilog syntax for Intel 8080, Z80 and LR35902 assembly. Intel syntax is not supported. +Millfork uses Zilog syntax for Intel 8080, Z80 and LR35902 assembly. + +**Work in progress**: +Intel syntax is not supported yet. +LR35902 instructions for faster access to the $FFxx addresses are not available yet. Indexing via the IX/IY register uses the following syntax: `IX(1)` @@ -20,8 +24,6 @@ LR35902 instructions that load/store the accumulator indirectly via HL and then Only instructions available on the current CPU architecture are available. Undocumented instructions are not supported, except for `SLL`. -LR35902 instructions for faster access to the $FFxx addresses are not available yet. - Labels have to be followed by a colon and they can optionally be on a separate line. Indentation is not important: diff --git a/src/main/scala/millfork/CompilationOptions.scala b/src/main/scala/millfork/CompilationOptions.scala index ee479c23..a56c93e2 100644 --- a/src/main/scala/millfork/CompilationOptions.scala +++ b/src/main/scala/millfork/CompilationOptions.scala @@ -37,7 +37,7 @@ case class CompilationOptions(platform: Platform, if (CpuFamily.forType(platform.cpu) != CpuFamily.I80) invalids ++= Set( EmitExtended80Opcodes, EmitZ80Opcodes, EmitSharpOpcodes, EmitIntel8080Opcodes, EmitEZ80Opcodes, - UseIxForStack, UseIyForStack, UseShadowRegistersForInterrupts) + UseIxForStack, UseIyForStack, UseShadowRegistersForInterrupts, UseIntelSyntaxForInput, UseIntelSyntaxForOutput) invalids = invalids.filter(flags) @@ -210,7 +210,7 @@ object Cpu extends Enumeration { case Sixteen => mosAlwaysDefaultFlags ++ Set(DecimalMode, EmitCmosOpcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, ReturnWordsViaAccumulator) case Intel8080 => - i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes) + i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, UseIntelSyntaxForOutput) case Z80 => i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts) case EZ80 => @@ -279,6 +279,8 @@ object CompilationFlag extends Enumeration { UseShadowRegistersForInterrupts, UseIxForStack, UseIyForStack, UseIxForScratch, UseIyForScratch, + UseIntelSyntaxForInput, + UseIntelSyntaxForOutput, // optimization options: DangerousOptimizations, InlineFunctions, InterproceduralOptimization, OptimizeForSize, OptimizeForSpeed, OptimizeForSonicSpeed, // memory allocation options @@ -313,6 +315,7 @@ object CompilationFlag extends Enumeration { "ix_scratch" -> UseIxForScratch, "iy_scratch" -> UseIyForScratch, "use_shadow_registers_for_irq" -> UseShadowRegistersForInterrupts, + "output_intel_syntax" -> UseIntelSyntaxForOutput, "ipo" -> InterproceduralOptimization, "inline" -> InlineFunctions, "dangerous_optimizations" -> DangerousOptimizations, diff --git a/src/main/scala/millfork/Main.scala b/src/main/scala/millfork/Main.scala index 96ad2fad..2f95314b 100644 --- a/src/main/scala/millfork/Main.scala +++ b/src/main/scala/millfork/Main.scala @@ -451,6 +451,9 @@ object Main { flag("--single-threaded").action(c => c.changeFlag(CompilationFlag.SingleThreaded, true) ).description("Run the compiler in a single thread.") + boolean("-foutput_intel_syntax", "-foutput_zilog_syntax").action((c,v) => + c.changeFlag(CompilationFlag.UseIntelSyntaxForOutput, v) + ).description("Select syntax for assembly output.") flag("--help").action(c => { println("millfork version " + BuildInfo.version) diff --git a/src/main/scala/millfork/assembly/z80/ZLine.scala b/src/main/scala/millfork/assembly/z80/ZLine.scala index ba47cfef..f5fed503 100644 --- a/src/main/scala/millfork/assembly/z80/ZLine.scala +++ b/src/main/scala/millfork/assembly/z80/ZLine.scala @@ -238,6 +238,27 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case ZRegister.MEM_DE => "(DE)" } + private def asIntelAssemblyString(r: ZRegister.Value): String = r match { + case ZRegister.A => "A" + case ZRegister.B => "B" + case ZRegister.C => "C" + case ZRegister.D => "D" + case ZRegister.E => "E" + case ZRegister.H => "H" + case ZRegister.L => "L" + case ZRegister.AF => "PSW" + case ZRegister.BC => "B" + case ZRegister.DE => "D" + case ZRegister.HL => "H" + case ZRegister.SP => "SP" + case ZRegister.MEM_ABS_8 => s"$parameter" + case ZRegister.MEM_ABS_16 => s"$parameter" + case ZRegister.IMM_8 => s"$parameter" + case ZRegister.IMM_16 => s"$parameter" + case ZRegister.MEM_HL => "M" + case _ => "???" + } + override def toString: String = { import ZOpcode._ opcode match { @@ -248,7 +269,7 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta case DISCARD_DE => " ; DISCARD_DE" case DISCARD_IX => " ; DISCARD_IX" case DISCARD_IY => " ; DISCARD_IY" - case BYTE => " !byte " + parameter.toString // TODO: format? + case BYTE => " DB " + parameter.toString // TODO: format? case LABEL => parameter.toString + ":" case RST => s" RST $parameter" case IM => s" IM $parameter" @@ -320,6 +341,173 @@ case class ZLine(opcode: ZOpcode.Value, registers: ZRegisters, parameter: Consta } } + def toIntelString: String = { + import ZOpcode._ + import ZRegister._ + val result = opcode match { + case LABEL => parameter.toString + ":" + case DISCARD_A => " ; DISCARD_A" + case DISCARD_HL => " ; DISCARD_HL" + case DISCARD_F => " ; DISCARD_F" + case DISCARD_BC => " ; DISCARD_BC" + case DISCARD_DE => " ; DISCARD_DE" + case DISCARD_IX => " ; DISCARD_IX" + case DISCARD_IY => " ; DISCARD_IY" + case BYTE => " DB " + parameter.toString + case LD => registers match { + case TwoRegisters(target, IMM_8) => s" MVI ${asIntelAssemblyString(target)}, ${parameter.toIntelString}" + case TwoRegisters(A, MEM_ABS_8) => s" LDA ${parameter.toIntelString}" + case TwoRegisters(MEM_ABS_8, A) => s" STA ${parameter.toIntelString}" + case TwoRegisters(A, MEM_BC) => " LDAX B" + case TwoRegisters(MEM_BC, A) => " STAX B" + case TwoRegisters(A, MEM_DE) => " LDAX D" + case TwoRegisters(MEM_DE, A) => " STAX D" + case TwoRegisters(target, source) => s" MOV ${asIntelAssemblyString(target)}, ${asIntelAssemblyString(source)}" + case _ => "???" + } + case LD_16 => registers match { + case TwoRegisters(SP, HL) => " SPHL" + case TwoRegisters(target, IMM_16) => s" LXI ${asIntelAssemblyString(target)}, ${parameter.toIntelString}" + case TwoRegisters(HL, MEM_ABS_16) => s" LHLD ${parameter.toIntelString}" + case TwoRegisters(MEM_ABS_16, HL) => s" SHLD ${parameter.toIntelString}" + case _ => "???" + } + case ADD_16 => registers match { + case TwoRegisters(HL, source) => s" DAD ${asIntelAssemblyString(source)}" + case _ => "???" + } + case DEC_16 => registers match { + case OneRegister(register) => s" DCX ${asIntelAssemblyString(register)}" + case _ => "???" + } + case INC_16 => registers match { + case OneRegister(register) => s" INX ${asIntelAssemblyString(register)}" + case _ => "???" + } + case DEC => registers match { + case OneRegister(register) => s" DCR ${asIntelAssemblyString(register)}" + case _ => "???" + } + case INC => registers match { + case OneRegister(register) => s" INR ${asIntelAssemblyString(register)}" + case _ => "???" + } + case PUSH => registers match { + case OneRegister(register) => s" PUSH ${asIntelAssemblyString(register)}" + case _ => "???" + } + case POP => registers match { + case OneRegister(register) => s" POP ${asIntelAssemblyString(register)}" + case _ => "???" + } + case DAA => " DAA" + case RLA => " RAL" + case RLCA => " RLC" + case RRA => " RAR" + case RRCA => " RRC" + case HALT => " HLT" + case RET => registers match { + case NoRegisters => " RET" + case IfFlagClear(ZFlag.C) => " RNC" + case IfFlagClear(ZFlag.Z) => " RNZ" + case IfFlagClear(ZFlag.S) => " RP" + case IfFlagClear(ZFlag.P) => " RPO" + case IfFlagSet(ZFlag.C) => " RC" + case IfFlagSet(ZFlag.Z) => " RZ" + case IfFlagSet(ZFlag.S) => " RM" + case IfFlagSet(ZFlag.P) => " RPE" + case _ => "???" + } + case JP => registers match { + case OneRegister(HL) => " PCHL" + case NoRegisters => s" JMP ${parameter.toIntelString}" + case IfFlagClear(ZFlag.C) => s" JNC ${parameter.toIntelString}" + case IfFlagClear(ZFlag.Z) => s" JNZ ${parameter.toIntelString}" + case IfFlagClear(ZFlag.S) => s" JP ${parameter.toIntelString}" + case IfFlagClear(ZFlag.P) => s" JPO ${parameter.toIntelString}" + case IfFlagSet(ZFlag.C) => s" JC ${parameter.toIntelString}" + case IfFlagSet(ZFlag.Z) => s" JZ ${parameter.toIntelString}" + case IfFlagSet(ZFlag.S) => s" JM ${parameter.toIntelString}" + case IfFlagSet(ZFlag.P) => s" JPE ${parameter.toIntelString}" + case _ => "???" + } + case CALL => registers match { + case NoRegisters => s" CALL ${parameter.toIntelString}" + case IfFlagClear(ZFlag.C) => s" CNC ${parameter.toIntelString}" + case IfFlagClear(ZFlag.Z) => s" CNZ ${parameter.toIntelString}" + case IfFlagClear(ZFlag.S) => s" CP ${parameter.toIntelString}" + case IfFlagClear(ZFlag.P) => s" CPO ${parameter.toIntelString}" + case IfFlagSet(ZFlag.C) => s" CC ${parameter.toIntelString}" + case IfFlagSet(ZFlag.Z) => s" CZ ${parameter.toIntelString}" + case IfFlagSet(ZFlag.S) => s" CM ${parameter.toIntelString}" + case IfFlagSet(ZFlag.P) => s" CPE ${parameter.toIntelString}" + case _ => "???" + } + case ADD => registers match { + case OneRegister(IMM_8) => s" ADI ${parameter.toIntelString}" + case OneRegister(register) => s" ADD ${asIntelAssemblyString(register)}" + case _ => "???" + } + case ADC => registers match { + case OneRegister(IMM_8) => s" ACI ${parameter.toIntelString}" + case OneRegister(register) => s" ADC ${asIntelAssemblyString(register)}" + case _ => "???" + } + case SUB => registers match { + case OneRegister(IMM_8) => s" SUI ${parameter.toIntelString}" + case OneRegister(register) => s" SUB ${asIntelAssemblyString(register)}" + case _ => "???" + } + case SBC => registers match { + case OneRegister(IMM_8) => s" SBI ${parameter.toIntelString}" + case OneRegister(register) => s" SBB ${asIntelAssemblyString(register)}" + case _ => "???" + } + case AND => registers match { + case OneRegister(IMM_8) => s" ANI ${parameter.toIntelString}" + case OneRegister(register) => s" ANA ${asIntelAssemblyString(register)}" + case _ => "???" + } + case OR => registers match { + case OneRegister(IMM_8) => s" ORI ${parameter.toIntelString}" + case OneRegister(register) => s" ORA ${asIntelAssemblyString(register)}" + case _ => "???" + } + case XOR => registers match { + case OneRegister(IMM_8) => s" XRI ${parameter.toIntelString}" + case OneRegister(register) => s" XRA ${asIntelAssemblyString(register)}" + case _ => "???" + } + case CP => registers match { + case OneRegister(IMM_8) => s" CPI ${parameter.toIntelString}" + case OneRegister(register) => s" CMP ${asIntelAssemblyString(register)}" + case _ => "???" + } + case EX_SP => registers match { + case OneRegister(HL) => s" XTHL" + case _ => "???" + } + case RST => parameter match { + case NumericConstant(n, _) if n % 8 == 0 => s" RST ${n / 8}" + case _ => "???" + } + case IN_IMM => s" IN ${parameter.toIntelString}" + case OUT_IMM => s" OUT ${parameter.toIntelString}" + case EI => " EI" + case DI => " EI" + case EX_DE_HL => " XCHG" + case NOP => " NOP" + case CPL => " CMA" + case SCF => " STC" + case CCF => " CMC" + case EI => " EI" + case EI => " EI" + case EI => " EI" + case _ => "???" + } + if (result.contains("???")) s" ??? (${this.toString.stripPrefix(" ")})" else result + } + def readsRegister(r: ZRegister.Value): Boolean = { import ZOpcode._ import ZRegister._ diff --git a/src/main/scala/millfork/env/Constant.scala b/src/main/scala/millfork/env/Constant.scala index 2f23e3a5..2d3371b3 100644 --- a/src/main/scala/millfork/env/Constant.scala +++ b/src/main/scala/millfork/env/Constant.scala @@ -17,6 +17,9 @@ import millfork.error.ConsoleLogger import millfork.node.Position sealed trait Constant { + + def toIntelString: String + def isProvablyZero: Boolean = false def isProvably(value: Int): Boolean = false def isProvablyNonnegative: Boolean = false @@ -101,12 +104,15 @@ case class AssertByte(c: Constant) extends Constant { override def quickSimplify: Constant = AssertByte(c.quickSimplify) override def fitsInto(typ: Type): Boolean = true + override def toIntelString: String = c.toIntelString } case class UnexpandedConstant(name: String, requiredSize: Int) extends Constant { override def isRelatedTo(v: Thing): Boolean = false override def toString: String = name + + override def toIntelString: String = name } case class NumericConstant(value: Long, requiredSize: Int) extends Constant { @@ -135,6 +141,11 @@ case class NumericConstant(value: Long, requiredSize: Int) extends Constant { override def toString: String = if (value > 9) value.formatted("$%X") else value.toString + override def toIntelString: String = if (value > 9) { + val tmp = value.formatted("%Xh") + if (tmp(0) > '9') "0" + tmp else tmp + } else value.toString + override def isRelatedTo(v: Thing): Boolean = false override def fitsInto(typ: Type): Boolean = { @@ -166,6 +177,8 @@ case class MemoryAddressConstant(var thing: ThingInMemory) extends Constant { override def toString: String = thing.name + override def toIntelString: String = thing.name + override def isRelatedTo(v: Thing): Boolean = thing.name == v.name } @@ -186,12 +199,17 @@ case class SubbyteConstant(base: Constant, index: Int) extends Constant { override def isProvablyNonnegative: Boolean = true - override def toString: String = base + (index match { - case 0 => ".lo" - case 1 => ".hi" - case 2 => ".b2" - case 3 => ".b3" - }) + override def toString: String = index match { + case 0 => s"lo($base)" + case 1 => s"hi($base)" + case i => s"b$i($base)" + } + + override def toIntelString: String = index match { + case 0 => s"lo(${base.toIntelString})" + case 1 => s"hi($base.toIntelString)" + case i => s"b$i($base.toIntelString)" + } override def isRelatedTo(v: Thing): Boolean = base.isRelatedTo(v) } @@ -373,36 +391,70 @@ case class CompoundConstant(operator: MathOperator.Value, lhs: Constant, rhs: Co } } - private def plhs = lhs match { - case _: NumericConstant | _: MemoryAddressConstant => lhs + private def plhs: String = lhs match { + case _: NumericConstant | _: MemoryAddressConstant => lhs.toString case _ => "(" + lhs + ')' } - private def prhs = lhs match { - case _: NumericConstant | _: MemoryAddressConstant => rhs + private def prhs: String = lhs match { + case _: NumericConstant | _: MemoryAddressConstant => rhs.toString case _ => "(" + rhs + ')' } + + private def plhis: String = lhs match { + case _: NumericConstant | _: MemoryAddressConstant => lhs.toIntelString + case _ => "(" + lhs.toIntelString + ')' + } + + private def prhis: String = lhs match { + case _: NumericConstant | _: MemoryAddressConstant => rhs.toIntelString + case _ => "(" + rhs.toIntelString + ')' + } + override def toString: String = { operator match { - case Plus => f"$plhs + $prhs" - case Plus9 => f"nonet($plhs + $prhs)" - case Minus => f"$plhs - $prhs" - case Times => f"$plhs * $prhs" - case Shl => f"$plhs << $prhs" - case Shr => f"$plhs >> $prhs" - case Shl9 => f"nonet($plhs << $prhs)" - case Shr9 => f"$plhs >>>> $prhs" - case DecimalPlus => f"$plhs +' $prhs" - case DecimalPlus9 => f"nonet($plhs +' $prhs)" - case DecimalMinus => f"$plhs -' $prhs" - case DecimalTimes => f"$plhs *' $prhs" - case DecimalShl => f"$plhs <<' $prhs" - case DecimalShl9 => f"nonet($plhs <<' $prhs)" - case DecimalShr => f"$plhs >>' $prhs" - case And => f"$plhs & $prhs" - case Or => f"$plhs | $prhs" - case Exor => f"$plhs ^ $prhs" + case Plus => s"$plhs + $prhs" + case Plus9 => s"nonet($plhs + $prhs)" + case Minus => s"$plhs - $prhs" + case Times => s"$plhs * $prhs" + case Shl => s"$plhs << $prhs" + case Shr => s"$plhs >> $prhs" + case Shl9 => s"nonet($plhs << $prhs)" + case Shr9 => s"$plhs >>>> $prhs" + case DecimalPlus => s"$plhs +' $prhs" + case DecimalPlus9 => s"nonet($plhs +' $prhs)" + case DecimalMinus => s"$plhs -' $prhs" + case DecimalTimes => s"$plhs *' $prhs" + case DecimalShl => s"$plhs <<' $prhs" + case DecimalShl9 => s"nonet($plhs <<' $prhs)" + case DecimalShr => s"$plhs >>' $prhs" + case And => s"$plhs & $prhs" + case Or => s"$plhs | $prhs" + case Exor => s"$plhs ^ $prhs" + } + } + + override def toIntelString: String = { + operator match { + case Plus => s"$plhis + $prhis" + case Plus9 => s"nonet($plhis + $prhis)" + case Minus => s"$plhis - $prhis" + case Times => s"$plhis * $prhis" + case Shl => s"$plhis << $prhis" + case Shr => s"$plhis >> $prhis" + case Shl9 => s"nonet($plhis << $prhis)" + case Shr9 => s"$plhis >>>> $prhis" + case DecimalPlus => s"$plhis +' $prhis" + case DecimalPlus9 => s"nonet($plhis +' $prhis)" + case DecimalMinus => s"$plhis -' $prhis" + case DecimalTimes => s"$plhis *' $prhis" + case DecimalShl => s"$plhis <<' $prhis" + case DecimalShl9 => s"nonet($plhis <<' $prhis)" + case DecimalShr => s"$plhis >>' $prhis" + case And => s"$plhis & $prhis" + case Or => s"$plhis | $prhis" + case Exor => s"$plhis ^ $prhis" } } diff --git a/src/main/scala/millfork/output/AbstractAssembler.scala b/src/main/scala/millfork/output/AbstractAssembler.scala index a92948d8..ffd5d6ae 100644 --- a/src/main/scala/millfork/output/AbstractAssembler.scala +++ b/src/main/scala/millfork/output/AbstractAssembler.scala @@ -6,8 +6,10 @@ import millfork.env._ import millfork.error.{ConsoleLogger, Logger} import millfork.node.{CallGraph, NiceFunctionProperty, Program} import millfork._ +import millfork.assembly.z80.ZLine import scala.collection.mutable +import scala.math.Integral.Implicits.infixIntegralOps /** * @author Karol Stasiak @@ -177,6 +179,8 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program private def asDecimal(a: Long, b: Long, f: (Long, Long) => Long): Long = storeDecimalValueInNormalRespresentation(f(parseNormalToDecimalValue(a), parseNormalToDecimalValue(b))) + def bytePseudoopcode: String + def assemble(callGraph: CallGraph, optimizations: Seq[AssemblyOptimization[T]], options: CompilationOptions): AssemblerOutput = { mem.programName = options.outputFileName.getOrElse("MILLFORK") val platform = options.platform @@ -266,7 +270,7 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program index += 1 } items.grouped(16).foreach { group => - assembly.append(" !byte " + group.map(expr => env.eval(expr) match { + assembly.append(" " + bytePseudoopcode + " " + group.map(expr => env.eval(expr) match { case Some(c) => c.quickSimplify.toString case None => "" }).mkString(", ")) @@ -490,7 +494,16 @@ abstract class AbstractAssembler[T <: AbstractCode](private val program: Program assOut.append("* = $" + startFrom.toHexString) for (instr <- code) { if (instr.isPrintable) { - assOut.append(instr.toString) + if (options.flag(CompilationFlag.UseIntelSyntaxForOutput)) { + instr match { + case zline: ZLine => + assOut.append(zline.toIntelString) + case _ => + assOut.append(instr.toString) + } + } else { + assOut.append(instr.toString) + } } index = emitInstruction(bank, options, index, instr) } diff --git a/src/main/scala/millfork/output/MosAssembler.scala b/src/main/scala/millfork/output/MosAssembler.scala index 156cde05..eb801474 100644 --- a/src/main/scala/millfork/output/MosAssembler.scala +++ b/src/main/scala/millfork/output/MosAssembler.scala @@ -164,6 +164,8 @@ class MosAssembler(program: Program, case _ => true } } + + override def bytePseudoopcode: String = "!byte" } diff --git a/src/main/scala/millfork/output/Z80Assembler.scala b/src/main/scala/millfork/output/Z80Assembler.scala index 59bba5d1..9119e08e 100644 --- a/src/main/scala/millfork/output/Z80Assembler.scala +++ b/src/main/scala/millfork/output/Z80Assembler.scala @@ -615,6 +615,8 @@ class Z80Assembler(program: Program, override def gatherNiceFunctionProperties(niceFunctionProperties: mutable.Set[(NiceFunctionProperty, String)], functionName: String, code: List[ZLine]): Unit = { // do nothing yet } + + override def bytePseudoopcode: String = "DB" } object Z80Assembler { diff --git a/src/main/scala/millfork/parser/AbstractSourceLoadingQueue.scala b/src/main/scala/millfork/parser/AbstractSourceLoadingQueue.scala index bdca6aad..5f4956c7 100644 --- a/src/main/scala/millfork/parser/AbstractSourceLoadingQueue.scala +++ b/src/main/scala/millfork/parser/AbstractSourceLoadingQueue.scala @@ -51,16 +51,23 @@ abstract class AbstractSourceLoadingQueue[T](val initialFilenames: List[String], options.log.fatal(s"Module `$moduleName` not found", position) } - def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long]) : MfParser[T] + def supportedPragmas: Set[String] + + def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long], pragmas: Set[String]) : MfParser[T] def parseModule(moduleName: String, includePath: List[String], why: Either[Option[Position], String]): Unit = { val filename: String = why.fold(p => lookupModuleFile(includePath, moduleName, p), s => s) options.log.debug(s"Parsing $filename") val path = Paths.get(filename) val parentDir = path.toFile.getAbsoluteFile.getParent - val (src, featureConstants) = Preprocessor(options, Files.readAllLines(path, StandardCharsets.UTF_8).toIndexedSeq) + val PreprocessingResult(src, featureConstants, pragmas) = Preprocessor(options, Files.readAllLines(path, StandardCharsets.UTF_8).toIndexedSeq) + for (pragma <- pragmas) { + if (!supportedPragmas(pragma._1)) { + options.log.warn(s"Unsupported pragma: #pragma $pragma", Some(Position(moduleName, pragma._2, 1, 0))) + } + } val shortFileName = path.getFileName.toString - val parser = createParser(shortFileName, src, parentDir, featureConstants) + val parser = createParser(shortFileName, src, parentDir, featureConstants, pragmas.keySet) options.log.addSource(shortFileName, src.lines.toIndexedSeq) parser.toAst match { case Success(prog, _) => diff --git a/src/main/scala/millfork/parser/MosSourceLoadingQueue.scala b/src/main/scala/millfork/parser/MosSourceLoadingQueue.scala index 1e0112b6..804978e4 100644 --- a/src/main/scala/millfork/parser/MosSourceLoadingQueue.scala +++ b/src/main/scala/millfork/parser/MosSourceLoadingQueue.scala @@ -10,7 +10,7 @@ class MosSourceLoadingQueue(initialFilenames: List[String], includePath: List[String], options: CompilationOptions) extends AbstractSourceLoadingQueue[AssemblyLine](initialFilenames, includePath, options) { - override def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long]): MfParser[AssemblyLine] = + override def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long], pragmas: Set[String]): MfParser[AssemblyLine] = MosParser(filename, src, parentDir, options, featureConstants) def enqueueStandardModules(): Unit = { @@ -19,4 +19,5 @@ class MosSourceLoadingQueue(initialFilenames: List[String], } } + override val supportedPragmas: Set[String] = Set() } diff --git a/src/main/scala/millfork/parser/Preprocessor.scala b/src/main/scala/millfork/parser/Preprocessor.scala index 37460412..7b7900de 100644 --- a/src/main/scala/millfork/parser/Preprocessor.scala +++ b/src/main/scala/millfork/parser/Preprocessor.scala @@ -10,17 +10,20 @@ import scala.collection.mutable /** * @author Karol Stasiak */ + +case class PreprocessingResult(source: String, featureConstants: Map[String, Long], pragmas: Map[String, Int]) + object Preprocessor { private val Regex = raw"\A\s*#\s*([a-z]+)\s*(.*?)\s*\z".r - def preprocessForTest(options: CompilationOptions, code: String): (String, Map[String, Long]) = { + def preprocessForTest(options: CompilationOptions, code: String): PreprocessingResult = { apply(options, code.lines.toSeq) } case class IfContext(hadEnabled: Boolean, hadElse: Boolean, enabledBefore: Boolean) - def apply(options: CompilationOptions, lines: Seq[String]): (String, Map[String, Long]) = { + def apply(options: CompilationOptions, lines: Seq[String]): PreprocessingResult = { val platform = options.platform val log = options.log // if (log.traceEnabled) { @@ -30,6 +33,7 @@ object Preprocessor { // } val result = mutable.ListBuffer[String]() val featureConstants = mutable.Map[String, Long]() + val pragmas = mutable.Map[String, Int]() var enabled = true val ifStack = mutable.Stack[IfContext]() var lineNo = 0 @@ -105,6 +109,9 @@ object Preprocessor { } ifStack.push(ifStack.pop().copy(hadEnabled = true, hadElse = true)) } + case "pragma" => + if (param == "") log.error("#pragma should", pos) + pragmas += param -> lineNo case _ => log.error("Invalid preprocessor directive: #" + keyword, pos) @@ -121,7 +128,7 @@ object Preprocessor { // case (line, i) => log.trace(f"${i + 1}%-4d $line%s") // } // } - (result.mkString("\n"), featureConstants.toMap) + PreprocessingResult(result.mkString("\n"), featureConstants.toMap, pragmas.toMap) } diff --git a/src/main/scala/millfork/parser/Z80Parser.scala b/src/main/scala/millfork/parser/Z80Parser.scala index d301f856..d88228f1 100644 --- a/src/main/scala/millfork/parser/Z80Parser.scala +++ b/src/main/scala/millfork/parser/Z80Parser.scala @@ -13,7 +13,16 @@ import millfork.node._ /** * @author Karol Stasiak */ -case class Z80Parser(filename: String, input: String, currentDirectory: String, options: CompilationOptions, featureConstants: Map[String, Long]) extends MfParser[ZLine](filename, input, currentDirectory, options, featureConstants) { +case class Z80Parser(filename: String, + input: String, + currentDirectory: String, + options: CompilationOptions, + featureConstants: Map[String, Long], + useIntelSyntax: Boolean) extends MfParser[ZLine](filename, input, currentDirectory, options, featureConstants) { + + if (useIntelSyntax) { + options.log.error("Parsing assembly with Intel syntax not supported yet") + } import MfParser._ @@ -60,7 +69,7 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String, "IY" -> ZRegister.IY, "iy" -> ZRegister.IY, "SP" -> ZRegister.SP, "sp" -> ZRegister.SP, ) - + private def param(allowAbsolute: Boolean, allowRI: Boolean = false): P[(ZRegister.Value, Option[Expression])] = asmExpressionWithParens.map { case (VariableExpression("R" | "r"), false) if allowRI => (ZRegister.R, None) case (VariableExpression("I" | "i"), false) if allowRI => (ZRegister.I, None) @@ -156,7 +165,7 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String, private val jumpConditionWithComma: P[ZRegisters] = (jumpCondition ~ "," ~/ HWS).?.map (_.getOrElse(NoRegisters)) private val jumpConditionWithoutComma: P[ZRegisters] = (jumpCondition ~/ HWS).?.map (_.getOrElse(NoRegisters)) - val asmInstruction: P[ExecutableStatement] = { + val zilogAsmInstruction: P[ExecutableStatement] = { import ZOpcode._ for { el <- elidable @@ -361,6 +370,10 @@ case class Z80Parser(filename: String, input: String, currentDirectory: String, } } + val intelAsmInstruction: P[ExecutableStatement] = null // TODO + + val asmInstruction: P[ExecutableStatement] = if (useIntelSyntax) intelAsmInstruction else zilogAsmInstruction + private def imm(opcode: ZOpcode.Value): P[(ZOpcode.Value, ZRegisters, Option[Expression], Expression)] = P("").map(_=>(opcode, NoRegisters, None, zero)) diff --git a/src/main/scala/millfork/parser/ZSourceLoadingQueue.scala b/src/main/scala/millfork/parser/ZSourceLoadingQueue.scala index bbccc1f6..a61bd295 100644 --- a/src/main/scala/millfork/parser/ZSourceLoadingQueue.scala +++ b/src/main/scala/millfork/parser/ZSourceLoadingQueue.scala @@ -11,11 +11,17 @@ class ZSourceLoadingQueue(initialFilenames: List[String], includePath: List[String], options: CompilationOptions) extends AbstractSourceLoadingQueue[ZLine](initialFilenames, includePath, options) { - override def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long]): MfParser[ZLine] = - Z80Parser(filename, src, parentDir, options, featureConstants) + override def createParser(filename: String, src: String, parentDir: String, featureConstants: Map[String, Long], pragmas: Set[String]): MfParser[ZLine] = { + var useIntelSyntax = options.flag(CompilationFlag.UseIntelSyntaxForInput) + if (pragmas("intel_syntax") && pragmas("zilog_syntax")) options.log.error("Conflicting pragmas: #pragma intel_syntax and #pragma zilog_syntax") + if (pragmas("zilog_syntax")) useIntelSyntax = false + if (pragmas("intel_syntax")) useIntelSyntax = true + Z80Parser(filename, src, parentDir, options, featureConstants, useIntelSyntax) + } def enqueueStandardModules(): Unit = { // TODO } + override val supportedPragmas: Set[String] = Set("intel_syntax", "zilog_syntax") } diff --git a/src/test/scala/millfork/test/emu/EmuRun.scala b/src/test/scala/millfork/test/emu/EmuRun.scala index c3a12d5c..d7d52405 100644 --- a/src/test/scala/millfork/test/emu/EmuRun.scala +++ b/src/test/scala/millfork/test/emu/EmuRun.scala @@ -16,7 +16,7 @@ import millfork.error.{ConsoleLogger, Logger} import millfork.node.StandardCallGraph import millfork.node.opt.NodeOptimization import millfork.output.{MemoryBank, MosAssembler} -import millfork.parser.{MosParser, Preprocessor} +import millfork.parser.{MosParser, PreprocessingResult, Preprocessor} import millfork.{CompilationFlag, CompilationOptions, CpuFamily, JobContext} import org.scalatest.Matchers @@ -121,7 +121,7 @@ class EmuRun(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimization], if (source.contains("import zp_reg")) effectiveSource += Files.readAllLines(Paths.get("include/zp_reg.mfk"), StandardCharsets.US_ASCII).asScala.mkString("\n", "\n", "") log.setSource(Some(effectiveSource.lines.toIndexedSeq)) - val (preprocessedSource, features) = Preprocessor.preprocessForTest(options, effectiveSource) + val PreprocessingResult(preprocessedSource, features, _) = Preprocessor.preprocessForTest(options, effectiveSource) val parserF = MosParser("", preprocessedSource, "", options, features) parserF.toAst match { case Success(unoptimized, _) => diff --git a/src/test/scala/millfork/test/emu/EmuZ80Run.scala b/src/test/scala/millfork/test/emu/EmuZ80Run.scala index ae6a1206..1d5d8833 100644 --- a/src/test/scala/millfork/test/emu/EmuZ80Run.scala +++ b/src/test/scala/millfork/test/emu/EmuZ80Run.scala @@ -13,7 +13,7 @@ import millfork.error.ConsoleLogger import millfork.node.StandardCallGraph import millfork.node.opt.NodeOptimization import millfork.output.{MemoryBank, Z80Assembler} -import millfork.parser.{Preprocessor, Z80Parser} +import millfork.parser.{PreprocessingResult, Preprocessor, Z80Parser} import millfork.{CompilationFlag, CompilationOptions, CpuFamily, JobContext} import millfork.compiler.z80.Z80Compiler import org.scalatest.Matchers @@ -46,8 +46,9 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio var effectiveSource = source if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}" log.setSource(Some(effectiveSource.lines.toIndexedSeq)) - val (preprocessedSource, features) = Preprocessor.preprocessForTest(options, effectiveSource) - val parserF = Z80Parser("", preprocessedSource, "", options, features) + val PreprocessingResult(preprocessedSource, features, pragmas) = Preprocessor.preprocessForTest(options, effectiveSource) + val parserF = Z80Parser("", preprocessedSource, "", options, features, false) + //if (pragmas.contains("intel_syntax")) true else if (pragmas.contains("zilog_syntax")) false else options.flag(CompilationFlag.UseIntelSyntax)) parserF.toAst match { case Success(unoptimized, _) => log.assertNoErrors("Parse failed") diff --git a/src/test/scala/millfork/test/emu/ShouldNotCompile.scala b/src/test/scala/millfork/test/emu/ShouldNotCompile.scala index 05687859..5abf66c1 100644 --- a/src/test/scala/millfork/test/emu/ShouldNotCompile.scala +++ b/src/test/scala/millfork/test/emu/ShouldNotCompile.scala @@ -8,7 +8,7 @@ import millfork.compiler.{CompilationContext, LabelGenerator} import millfork.compiler.mos.MosCompiler import millfork.env.{Environment, InitializedArray, InitializedMemoryVariable, NormalFunction} import millfork.node.StandardCallGraph -import millfork.parser.{MosParser, Preprocessor} +import millfork.parser.{MosParser, PreprocessingResult, Preprocessor} import millfork._ import org.scalatest.Matchers @@ -36,7 +36,7 @@ object ShouldNotCompile extends Matchers { if (source.contains("import zp_reg")) effectiveSource += Files.readAllLines(Paths.get("include/zp_reg.mfk"), StandardCharsets.US_ASCII).asScala.mkString("\n", "\n", "") log.setSource(Some(effectiveSource.lines.toIndexedSeq)) - val (preprocessedSource, features) = Preprocessor.preprocessForTest(options, effectiveSource) + val PreprocessingResult(preprocessedSource, features, _) = Preprocessor.preprocessForTest(options, effectiveSource) val parserF = MosParser("", preprocessedSource, "", options, features) parserF.toAst match { case Success(program, _) =>