1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-07-06 15:29:23 +00:00

Z80: Intel syntax support

This commit is contained in:
Karol Stasiak 2018-08-03 13:23:37 +02:00
parent e393a3de9c
commit fab1cafec3
15 changed files with 592 additions and 70 deletions

View File

@ -26,10 +26,6 @@ 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 <dir>;<dir>` The include directories. The current working directory is also an include directory. Those directories are searched for modules and platform definitions.
@ -40,6 +36,17 @@ Choose syntax for assembly output on 8080-like targets.
* `-D <feature>=<value>` Defines a feature value for the preprocessor.
* `-finput_intel_syntax`, `-finput_zilog_syntax`
Choose syntax for assembly sources on 8080-like targets.
Can be overridden by the source file itself using `#pragma`.
`.ini` equivalent: `input_intel_syntax`. Default: Intel (true) on Intel 8080, Zilog (false) otherwise.
* `-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.
* `--syntax=intel`, `--syntax=zilog` sets both previous options at once
## Verbosity options
* `-q` Suppress all messages except for errors.

View File

@ -48,7 +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)
(if targeting Intel 8080, use `--syntax=intel` or `--syntax=zilog` to choose the preferred assembly syntax)
* `-g` additionally generate a label file, in format compatible with VICE emulator

View File

@ -14,6 +14,9 @@ Octal: `0o172`
Hexadecimal: `$D323`, `0x2a2`
When using Intel syntax for inline assembly, another hexadecimal syntax is available: `0D323H`, `2a2h`.
It is not allowed in any other places.
## String literals
String literals can be used as either array initializers or expressions of type `pointer`.

View File

@ -103,4 +103,12 @@ To use such value in other files, consider this:
#use WIDESCREEN
const byte is_widescreen = WIDESCREEN
### `#pragma`
Changes the behaviour of the parser for the current file.
* `#pragma intel_syntax` interpret assembly using Intel syntax
* `#pragma zilog_syntax` interpret assembly using Zilog syntax

View File

@ -1,54 +1,56 @@
#pragma intel_syntax
inline asm void exit() {
? ld c, 0
? mvi c, 0
call 5
? ret
}
inline asm void putchar (byte e) {
? ld c, 2
? mvi c, 2
call 5
? ret
}
inline asm byte getchar() {
? ld c, 1
? mvi c, 1
call 5
? ret
}
#if 0
inline asm void putstr_cpm(pointer de) {
? ld c, 9
? mvi c, 9
call 5
? ret
}
#endif
inline asm byte readychar() {
? ld c, 11
? mvi c, 11
call 5
? ret
}
inline asm word cpm_version() {
? ld c, 12
? mvi c, 12
call 5
? ret
}
inline asm void reset_disk_system() {
? ld c, 13
? mvi c, 13
call 5
? ret
}
inline asm byte current_disk() {
? ld c,25
? mvi c,25
call 5
? ret
}
#if 0
inline asm word console_mode(word de) {
? ld c, 109
? mvi c, 109
call 5
? ret
}

View File

@ -3,6 +3,8 @@
#warn stdio_zxspectrum module should be only used on ZX Spectrum-compatible targets
#endif
#pragma zilog_syntax
import stdio
void putstr(pointer str, byte len) {

View File

@ -4,6 +4,8 @@
#warn stdlib_i80 module should be only used on Intel 8080-like targets
#endif
#pragma zilog_syntax
macro asm void poke(word const addr, byte a) {
LD (addr), A
}

View File

@ -3,6 +3,8 @@
#warn zxspectrum module should be only used on ZX Spectrum-compatible targets
#endif
#pragma zilog_syntax
inline asm void putchar(byte a) {
rst $10
? ret

View File

@ -210,7 +210,7 @@ object Cpu extends Enumeration {
case Sixteen =>
mosAlwaysDefaultFlags ++ Set(DecimalMode, EmitCmosOpcodes, EmitEmulation65816Opcodes, EmitNative65816Opcodes, ReturnWordsViaAccumulator)
case Intel8080 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, UseIntelSyntaxForOutput)
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, UseIntelSyntaxForInput, UseIntelSyntaxForOutput)
case Z80 =>
i80AlwaysDefaultFlags ++ Set(EmitIntel8080Opcodes, EmitExtended80Opcodes, EmitZ80Opcodes, UseIxForStack, UseShadowRegistersForInterrupts)
case EZ80 =>
@ -316,6 +316,7 @@ object CompilationFlag extends Enumeration {
"iy_scratch" -> UseIyForScratch,
"use_shadow_registers_for_irq" -> UseShadowRegistersForInterrupts,
"output_intel_syntax" -> UseIntelSyntaxForOutput,
"input_intel_syntax" -> UseIntelSyntaxForInput,
"ipo" -> InterproceduralOptimization,
"inline" -> InlineFunctions,
"dangerous_optimizations" -> DangerousOptimizations,

View File

@ -285,6 +285,18 @@ object Main {
}
}.description("Define a feature value for the preprocessor.")
boolean("-finput_intel_syntax", "-finput_zilog_syntax").action((c,v) =>
c.changeFlag(CompilationFlag.UseIntelSyntaxForInput, v)
).description("Select syntax for assembly source input.")
boolean("-foutput_intel_syntax", "-foutput_zilog_syntax").action((c,v) =>
c.changeFlag(CompilationFlag.UseIntelSyntaxForOutput, v)
).description("Select syntax for assembly output.")
boolean("--syntax=intel", "--syntax=zilog").action((c,v) =>
c.changeFlag(CompilationFlag.UseIntelSyntaxForInput, v).changeFlag(CompilationFlag.UseIntelSyntaxForOutput, v)
).description("Select syntax for assembly input and output.")
endOfFlags("--").description("Marks the end of options.")
fluff("", "Verbosity options:", "")
@ -451,9 +463,6 @@ 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)

View File

@ -21,6 +21,8 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
var lastLabel = ""
protected val log: Logger = options.log
def allowIntelHexAtomsInAssembly: Boolean
def toAst: Parsed[Program] = program.parse(input + "\n\n\n")
private val lineStarts: Array[Int] = (0 +: input.zipWithIndex.filter(_._1 == '\n').map(_._2)).toArray
@ -88,8 +90,12 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
val literalAtom: P[LiteralExpression] = charAtom | binaryAtom | hexAtom | octalAtom | quaternaryAtom | decimalAtom
val literalAtomWithIntel: P[LiteralExpression] = charAtom | binaryAtom | hexAtom | octalAtom | quaternaryAtom | intelHexAtom | decimalAtom
val atom: P[Expression] = P(position() ~ (literalAtom | variableAtom | textLiteralAtom)).map{case (p,a) => a.pos(p)}
val atomWithIntel: P[Expression] = P(position() ~ (literalAtomWithIntel | variableAtom | textLiteralAtom)).map{case (p,a) => a.pos(p)}
val globalVariableDefinition: P[Seq[DeclarationStatement]] = variableDefinition(true)
val localVariableDefinition: P[Seq[DeclarationStatement]] = variableDefinition(false)
@ -99,8 +105,8 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
flags <- variableFlags ~ HWS
typ <- identifier ~ SWS
name <- identifier ~/ HWS ~/ Pass
addr <- ("@" ~/ HWS ~/ mfExpression(1)).?.opaque("<address>") ~ HWS
initialValue <- ("=" ~/ HWS ~/ mfExpression(1)).? ~ HWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).?.opaque("<address>") ~ HWS
initialValue <- ("=" ~/ HWS ~/ mfExpression(1, false)).? ~ HWS
_ <- &(EOL) ~/ ""
} yield {
Seq(VariableDeclarationStatement(name, typ,
@ -122,9 +128,9 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
}
def asmExpression: P[Expression] = (position() ~ NoCut(
("<" ~/ HWS ~ mfExpression(mathLevel)).map(e => HalfWordExpression(e, hiByte = false)) |
(">" ~/ HWS ~ mfExpression(mathLevel)).map(e => HalfWordExpression(e, hiByte = true)) |
mfExpression(mathLevel)
("<" ~/ HWS ~ mfExpression(mathLevel, allowIntelHexAtomsInAssembly)).map(e => HalfWordExpression(e, hiByte = false)) |
(">" ~/ HWS ~ mfExpression(mathLevel, allowIntelHexAtomsInAssembly)).map(e => HalfWordExpression(e, hiByte = true)) |
mfExpression(mathLevel, allowIntelHexAtomsInAssembly)
)).map { case (p, e) => e.pos(p) }
def asmExpressionWithParens: P[(Expression, Boolean)] = (position() ~ NoCut(
@ -147,7 +153,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
def asmParamDefinition: P[ParameterDeclaration]
def arrayListElement: P[ArrayContents] = arrayStringContents | arrayProcessedContents | arrayLoopContents | arrayFileContents | mfExpression(nonStatementLevel).map(e => LiteralContents(List(e)))
def arrayListElement: P[ArrayContents] = arrayStringContents | arrayProcessedContents | arrayLoopContents | arrayFileContents | mfExpression(nonStatementLevel, false).map(e => LiteralContents(List(e)))
def arrayProcessedContents: P[ArrayContents] = for {
_ <- "@" ~/ HWS
@ -176,10 +182,10 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
def arrayLoopContents: P[ArrayContents] = for {
identifier <- "for" ~ SWS ~/ identifier ~/ HWS ~ "," ~/ HWS ~ Pass
start <- mfExpression(nonStatementLevel) ~ HWS ~ "," ~/ HWS ~/ Pass
start <- mfExpression(nonStatementLevel, false) ~ HWS ~ "," ~/ HWS ~/ Pass
pos <- position("loop direction")
direction <- forDirection ~/ HWS ~/ "," ~/ HWS ~/ Pass
end <- mfExpression(nonStatementLevel)
end <- mfExpression(nonStatementLevel, false)
body <- AWS ~ arrayContents
} yield {
val fixedDirection = direction match {
@ -208,21 +214,27 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
p <- position()
bank <- bankDeclaration
name <- "array" ~ !letterOrDigit ~/ SWS ~ identifier ~ HWS
length <- ("[" ~/ AWS ~/ mfExpression(nonStatementLevel) ~ AWS ~ "]").? ~ HWS
addr <- ("@" ~/ HWS ~/ mfExpression(1)).? ~/ HWS
length <- ("[" ~/ AWS ~/ mfExpression(nonStatementLevel, false) ~ AWS ~ "]").? ~ HWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).? ~/ HWS
contents <- ("=" ~/ HWS ~/ arrayContents).? ~/ HWS
} yield Seq(ArrayDeclarationStatement(name, bank, length, addr, contents).pos(p))
def tightMfExpression: P[Expression] = P(mfParenExpr | functionCall | mfIndexedExpression | atom) // TODO
def tightMfExpression(allowIntelHex: Boolean): P[Expression] = {
val a = if (allowIntelHex) atomWithIntel else atom
P(mfParenExpr(allowIntelHex) | functionCall(allowIntelHex) | mfIndexedExpression | a) // TODO
}
def tightMfExpressionButNotCall: P[Expression] = P(mfParenExpr | mfIndexedExpression | atom) // TODO
def tightMfExpressionButNotCall(allowIntelHex: Boolean): P[Expression] = {
val a = if (allowIntelHex) atomWithIntel else atom
P(mfParenExpr(allowIntelHex) | mfIndexedExpression | a) // TODO
}
def mfExpression(level: Int): P[Expression] = {
def mfExpression(level: Int, allowIntelHex: Boolean): P[Expression] = {
val allowedOperators = mfOperatorsDropFlatten(level)
def inner: P[SeparatedList[Expression, String]] = {
for {
head <- tightMfExpression ~/ HWS
head <- tightMfExpression(allowIntelHex) ~/ HWS
maybeOperator <- StringIn(allowedOperators: _*).!.?
maybeTail <- maybeOperator.fold[P[Option[List[(String, Expression)]]]](Pass.map(_ => None))(o => (HWS ~/ inner ~/ HWS).map(x2 => Some((o -> x2.head) :: x2.tail)))
} yield {
@ -237,7 +249,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
xs.separators.distinct match {
case Nil =>
if (xs.tail.nonEmpty)
log.error("Too many different operators")
log.error("Too many different operators", xs.head.head.position)
p(xs.head, level + 1)
case List("+") | List("-") | List("+", "-") | List("-", "+") =>
SumExpression(xs.toPairList("+").map { case (op, value) => (op == "-", p(value, level + 1)) }, decimal = false)
@ -253,7 +265,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
case List(op) =>
FunctionCallExpression(op, xs.items.map(value => p(value, level + 1)))
case _ =>
log.error("Too many different operators")
log.error("Too many different operators", xs.head.head.position)
LiteralExpression(0, 1)
}
}
@ -269,24 +281,24 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
} yield rightOpt.fold(left)(right => SeparateBytesExpression(left, right).pos(p))
def mfParenExpr: P[Expression] = P("(" ~/ AWS ~/ mfExpression(nonStatementLevel) ~ AWS ~/ ")")
def mfParenExpr(allowIntelHex: Boolean): P[Expression] = P("(" ~/ AWS ~/ mfExpression(nonStatementLevel, allowIntelHex) ~ AWS ~/ ")")
def mfIndexedExpression: P[IndexedExpression] = for {
p <- position()
array <- identifier
index <- HWS ~ "[" ~/ AWS ~/ mfExpression(nonStatementLevel) ~ AWS ~/ "]"
index <- HWS ~ "[" ~/ AWS ~/ mfExpression(nonStatementLevel, false) ~ AWS ~/ "]"
} yield IndexedExpression(array, index).pos(p)
def functionCall: P[FunctionCallExpression] = for {
def functionCall(allowIntelHex: Boolean): P[FunctionCallExpression] = for {
p <- position()
name <- identifier
params <- HWS ~ "(" ~/ AWS ~/ mfExpression(nonStatementLevel).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ ""
params <- HWS ~ "(" ~/ AWS ~/ mfExpression(nonStatementLevel, allowIntelHex).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ ""
} yield FunctionCallExpression(name, params.toList).pos(p)
val expressionStatement: P[Seq[ExecutableStatement]] = mfExpression(0).map(x => Seq(ExpressionStatement(x)))
val expressionStatement: P[Seq[ExecutableStatement]] = mfExpression(0, false).map(x => Seq(ExpressionStatement(x)))
val assignmentStatement: P[Seq[ExecutableStatement]] =
(position() ~ mfLhsExpression ~ HWS ~ "=" ~/ HWS ~ mfExpression(1)).map {
(position() ~ mfLhsExpression ~ HWS ~ "=" ~/ HWS ~ mfExpression(1, false)).map {
case (p, l, r) => Seq(Assignment(l, r).pos(p))
}
@ -314,7 +326,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
def executableStatements: P[Seq[ExecutableStatement]] = ("{" ~/ AWS ~/ executableStatement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~ "}").map(_.flatten)
val dispatchLabel: P[ReturnDispatchLabel] =
("default" ~ !letterOrDigit ~/ AWS ~/ ("(" ~/ position("default branch range") ~ AWS ~/ mfExpression(nonStatementLevel).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?).map{
("default" ~ !letterOrDigit ~/ AWS ~/ ("(" ~/ position("default branch range") ~ AWS ~/ mfExpression(nonStatementLevel, false).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?).map{
case None => DefaultReturnDispatchLabel(None, None)
case Some((_, Seq())) => DefaultReturnDispatchLabel(None, None)
case Some((_, Seq(e))) => DefaultReturnDispatchLabel(None, Some(e))
@ -322,17 +334,17 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
case Some((pos, _)) =>
log.error("Invalid default branch declaration", Some(pos))
DefaultReturnDispatchLabel(None, None)
} | mfExpression(nonStatementLevel).rep(min = 0, sep = AWS ~ "," ~/ AWS).map(exprs => StandardReturnDispatchLabel(exprs.toList))
} | mfExpression(nonStatementLevel, false).rep(min = 0, sep = AWS ~ "," ~/ AWS).map(exprs => StandardReturnDispatchLabel(exprs.toList))
val dispatchBranch: P[ReturnDispatchBranch] = for {
pos <- position()
l <- dispatchLabel ~/ HWS ~/ "@" ~/ HWS
f <- tightMfExpressionButNotCall ~/ HWS
parameters <- ("(" ~/ position("dispatch actual parameters") ~ AWS ~/ mfExpression(nonStatementLevel).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?
f <- tightMfExpressionButNotCall(false) ~/ HWS
parameters <- ("(" ~/ position("dispatch actual parameters") ~ AWS ~/ mfExpression(nonStatementLevel, false).rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?
} yield ReturnDispatchBranch(l, f, parameters.map(_._2.toList).getOrElse(Nil)).pos(pos)
val dispatchStatementBody: P[Seq[ExecutableStatement]] = for {
indexer <- "[" ~/ AWS ~/ mfExpression(nonStatementLevel) ~/ AWS ~/ "]" ~/ AWS
indexer <- "[" ~/ AWS ~/ mfExpression(nonStatementLevel, false) ~/ AWS ~/ "]" ~/ AWS
_ <- position("dispatch statement body")
parameters <- ("(" ~/ position("dispatch parameters") ~ AWS ~/ mfLhsExpression.rep(min = 0, sep = AWS ~ "," ~/ AWS) ~ AWS ~/ ")" ~/ "").?
_ <- AWS ~/ position("dispatch statement body") ~/ "{" ~/ AWS
@ -340,24 +352,24 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
_ <- AWS ~/ "}"
} yield Seq(ReturnDispatchStatement(indexer, parameters.map(_._2.toList).getOrElse(Nil), branches.toList))
val returnOrDispatchStatement: P[Seq[ExecutableStatement]] = "return" ~ !letterOrDigit ~/ HWS ~ (dispatchStatementBody | mfExpression(nonStatementLevel).?.map(ReturnStatement).map(Seq(_)))
val returnOrDispatchStatement: P[Seq[ExecutableStatement]] = "return" ~ !letterOrDigit ~/ HWS ~ (dispatchStatementBody | mfExpression(nonStatementLevel, false).?.map(ReturnStatement).map(Seq(_)))
def ifStatement: P[Seq[ExecutableStatement]] = for {
condition <- "if" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel)
condition <- "if" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel, false)
thenBranch <- AWS ~/ executableStatements
elseBranch <- (AWS ~ "else" ~/ AWS ~/ (ifStatement | executableStatements)).?
} yield Seq(IfStatement(condition, thenBranch.toList, elseBranch.getOrElse(Nil).toList))
def whileStatement: P[Seq[ExecutableStatement]] = for {
condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel)
condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel, false)
body <- AWS ~ executableStatements
} yield Seq(WhileStatement(condition, body.toList, Nil))
def forStatement: P[Seq[ExecutableStatement]] = for {
identifier <- "for" ~ SWS ~/ identifier ~/ HWS ~ "," ~/ HWS ~ Pass
start <- mfExpression(nonStatementLevel) ~ HWS ~ "," ~/ HWS ~/ Pass
start <- mfExpression(nonStatementLevel, false) ~ HWS ~ "," ~/ HWS ~/ Pass
direction <- forDirection ~/ HWS ~/ "," ~/ HWS ~/ Pass
end <- mfExpression(nonStatementLevel)
end <- mfExpression(nonStatementLevel, false)
body <- AWS ~ executableStatements
} yield Seq(ForStatement(identifier, start, end, direction, body.toList))
@ -366,7 +378,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
//noinspection MutatorLikeMethodIsParameterless
def doWhileStatement: P[Seq[ExecutableStatement]] = for {
body <- "do" ~ !letterOrDigit ~/ AWS ~ executableStatements ~/ AWS
condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel)
condition <- "while" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel, false)
} yield Seq(DoWhileStatement(body.toList, Nil, condition))
val functionDefinition: P[Seq[DeclarationStatement]] = for {
@ -377,7 +389,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
if !InvalidReturnTypes(returnType)
name <- identifier ~ HWS
params <- "(" ~/ AWS ~/ (if (flags("asm")) asmParamDefinition else paramDefinition).rep(sep = AWS ~ "," ~/ AWS) ~ AWS ~ ")" ~/ AWS
addr <- ("@" ~/ HWS ~/ mfExpression(1)).?.opaque("<address>") ~/ AWS
addr <- ("@" ~/ HWS ~/ mfExpression(1, false)).?.opaque("<address>") ~/ AWS
statements <- (externFunctionBody | (if (flags("asm")) asmStatements else statements).map(l => Some(l))) ~/ Pass
} yield {
if (flags("interrupt") && flags("macro")) log.error(s"Interrupt function `$name` cannot be macros", Some(p))
@ -408,7 +420,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
val enumVariant: P[(String, Option[Expression])] = for {
name <- identifier ~/ HWS
value <- ("=" ~/ HWS ~/ mfExpression(1)).? ~ HWS
value <- ("=" ~/ HWS ~/ mfExpression(1, false)).? ~ HWS
} yield name -> value
val enumVariants: P[List[(String, Option[Expression])]] =
@ -503,6 +515,20 @@ object MfParser {
LiteralExpression(value, size(value, s.length > 2, s.length > 4, s.length > 6))
}
val intelHexAtom: P[LiteralExpression] =
for {
minus <- "-".!.?
head <- CharIn("0123456789").!
tail <- CharsWhileIn("1234567890abcdefABCDEF", min = 1).!.opaque("<hex digits>")
_ <- P("h" | "H")
} yield {
// check for marking zero:
val s = if (head == "0" && tail.nonEmpty && tail.head >'9') tail else head + tail
val abs = parseLong(s, 16)
val value = sign(abs, minus.isDefined)
LiteralExpression(value, size(value, s.length > 2, s.length > 4, s.length > 6))
}
val octalAtom: P[LiteralExpression] =
for {
minus <- "-".!.?

View File

@ -14,6 +14,8 @@ case class MosParser(filename: String, input: String, currentDirectory: String,
import MfParser._
def allowIntelHexAtomsInAssembly: Boolean = false
// TODO: label and instruction in one line
val asmLabel: P[ExecutableStatement] = (identifier ~ HWS ~ ":" ~/ HWS).map(l => MosAssemblyStatement(Opcode.LABEL, AddrMode.DoesNotExist, VariableExpression(l), elidable = true))
@ -62,7 +64,7 @@ case class MosParser(filename: String, input: String, currentDirectory: String,
}
}
val asmMacro: P[ExecutableStatement] = ("+" ~/ HWS ~/ functionCall).map(ExpressionStatement)
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

View File

@ -4,7 +4,8 @@ import java.util.Locale
import fastparse.all.{parserApi, _}
import fastparse.core
import millfork.{CompilationFlag, CompilationOptions}
import millfork.assembly.z80
import millfork.{CompilationFlag, CompilationOptions, node}
import millfork.assembly.z80.{ZOpcode, _}
import millfork.env.{ByZRegister, Constant, ParamPassingConvention}
import millfork.error.ConsoleLogger
@ -20,12 +21,10 @@ case class Z80Parser(filename: String,
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._
def allowIntelHexAtomsInAssembly: Boolean = useIntelSyntax
private val zero = LiteralExpression(0, 1)
val appcSimple: P[ParamPassingConvention] = (P("hl" | "bc" | "de" | "a" | "b" | "c" | "d" | "e" | "h" | "l").! ~ !letterOrDigit).map {
@ -51,7 +50,7 @@ case class Z80Parser(filename: String,
// TODO: label and instruction in one line
val asmLabel: P[ExecutableStatement] = (identifier ~ HWS ~ ":" ~/ HWS).map(l => Z80AssemblyStatement(ZOpcode.LABEL, NoRegisters, None, VariableExpression(l), elidable = true))
val asmMacro: P[ExecutableStatement] = ("+" ~/ HWS ~/ functionCall).map(ExpressionStatement)
val asmMacro: P[ExecutableStatement] = ("+" ~/ HWS ~/ functionCall(false)).map(ExpressionStatement)
private val toRegister: Map[String, ZRegister.Value] = Map(
"A" -> ZRegister.A, "a" -> ZRegister.A,
@ -70,6 +69,25 @@ case class Z80Parser(filename: String,
"SP" -> ZRegister.SP, "sp" -> ZRegister.SP,
)
private val toIntelRegister8: Map[String, ZRegister.Value] = Map(
"A" -> ZRegister.A, "a" -> ZRegister.A,
"B" -> ZRegister.B, "b" -> ZRegister.B,
"C" -> ZRegister.C, "c" -> ZRegister.C,
"D" -> ZRegister.D, "d" -> ZRegister.D,
"E" -> ZRegister.E, "e" -> ZRegister.E,
"H" -> ZRegister.H, "h" -> ZRegister.H,
"L" -> ZRegister.L, "l" -> ZRegister.L,
"M" -> ZRegister.L, "m" -> ZRegister.MEM_HL,
)
private val toIntelRegister16: Map[String, ZRegister.Value] = Map(
"H" -> ZRegister.HL, "h" -> ZRegister.HL,
"PSW" -> ZRegister.AF, "psw" -> ZRegister.AF,
"B" -> ZRegister.BC, "b" -> ZRegister.BC,
"D" -> ZRegister.DE, "d" -> ZRegister.DE,
"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)
@ -83,6 +101,24 @@ case class Z80Parser(filename: String,
case (e, _) => (ZRegister.IMM_8, Some(e))
}
private def intel8: P[ZRegister.Value] = asmExpression.map {
case VariableExpression(r) if toIntelRegister8.contains(r) => toIntelRegister8(r)
case x =>
options.log.error("Invalid operand", x.position)
ZRegister.A
}
private def intel16(allowPsw: Boolean): P[ZRegister.Value] = asmExpression.map {
case x@VariableExpression(r) if toIntelRegister16.contains(r) =>
val result = toIntelRegister16(r)
if (result == ZRegister.AF && !allowPsw) options.log.error("Invalid operand", x.position)
if (result == ZRegister.SP && allowPsw) options.log.error("Invalid operand", x.position)
result
case x =>
log.error("Invalid operand", x.position)
ZRegister.HL
}
def mapOne8Register(op: ZOpcode.Value)(param: (ZRegister.Value, Option[Expression])): (ZOpcode.Value, OneRegister, Option[Expression], Expression) = param match {
case (reg@(ZRegister.MEM_IX_D | ZRegister.MEM_IY_D), Some(e)) => (op, OneRegister(reg), Some(e), zero)
case (reg, addr) => (op, OneRegister(reg), None, addr.getOrElse(zero))
@ -370,7 +406,132 @@ case class Z80Parser(filename: String,
}
}
val intelAsmInstruction: P[ExecutableStatement] = null // TODO
val intelAsmInstruction: P[ExecutableStatement] = {
import ZOpcode._
import ZRegister._
for {
el <- elidable
pos <- position()
opcode: String <- identifier ~/ HWS
tuple4: (ZOpcode.Value, ZRegisters, Option[Expression], Expression) <- opcode.toUpperCase(Locale.ROOT) match {
case "NOP" => imm(NOP)
case "RLC" => imm(RLCA)
case "RRC" => imm(RRCA)
case "RAL" => imm(RLA)
case "RAR" => imm(RRA)
case "DAA" => imm(DAA)
case "CMA" => imm(CPL)
case "STC" => imm(SCF)
case "CMC" => imm(CCF)
case "HLT" => imm(HALT)
case "EI" => imm(EI)
case "DI" => imm(DI)
case "XCHG" => imm(EX_DE_HL)
case "XTHL" => P("").map(_ => (EX_SP, OneRegister(HL), None, zero))
case "SPHL" => P("").map(_ => (LD_16, TwoRegisters(SP, HL), None, zero))
case "PCHL" => P("").map(_ => (JP, z80.OneRegister(HL), None, zero))
case "MOV" => (intel8 ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ intel8).map {
case (r1, r2) => (LD, TwoRegisters(r1, r2), None, zero)
}
case "LXI" => (intel16(false) ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map {
case (r, e) => (LD_16, TwoRegisters(r, IMM_16), None, e)
}
case "MVI" => (intel8 ~ HWS ~ position("comma").map(_ => ()) ~ "," ~/ HWS ~ asmExpression).map {
case (r, e) => (LD, TwoRegisters(r, IMM_8), None, e)
}
case "DAD" => intel16(false).map { r => (ADD_16, TwoRegisters(HL, r), None, zero)}
case "STAX" => intel16(false).map {
case r@(ZRegister.BC | ZRegister.DE) => (LD, TwoRegisters(r, A), None, zero)
case _ =>
log.error("Invalid parameters for STAX", Some(pos))
(NOP, NoRegisters, None, zero)
}
case "LDAX" => intel16(false).map {
case r@(ZRegister.BC | ZRegister.DE) => (LD, TwoRegisters(A, r), None, zero)
case _ =>
log.error("Invalid parameter for STAX", Some(pos))
(NOP, NoRegisters, None, zero)
}
case "INX" => intel16(false).map { r => (INC_16, OneRegister(r), None, zero)}
case "DCX" => intel16(false).map { r => (DEC_16, OneRegister(r), None, zero)}
case "POP" => intel16(true).map { r => (POP, OneRegister(r), None, zero)}
case "PUSH" => intel16(true).map { r => (PUSH, OneRegister(r), None, zero)}
case "INR" => intel8.map { r => (INC, OneRegister(r), None, zero)}
case "DCR" => intel8.map { r => (DEC, OneRegister(r), None, zero)}
case "ADD" => intel8.map { r => (ADD, OneRegister(r), None, zero)}
case "SUB" => intel8.map { r => (SUB, OneRegister(r), None, zero)}
case "ADC" => intel8.map { r => (ADC, OneRegister(r), None, zero)}
case "SBB" => intel8.map { r => (SBC, OneRegister(r), None, zero)}
case "ORA" => intel8.map { r => (OR, OneRegister(r), None, zero)}
case "ANA" => intel8.map { r => (AND, OneRegister(r), None, zero)}
case "XRA" => intel8.map { r => (XOR, OneRegister(r), None, zero)}
case "CMP" => intel8.map { r => (CP, OneRegister(r), None, zero)}
case "ADI" => asmExpression.map { e => (ADD, OneRegister(IMM_8), None, e)}
case "ACI" => asmExpression.map { e => (ADC, OneRegister(IMM_8), None, e)}
case "SUI" => asmExpression.map { e => (SUB, OneRegister(IMM_8), None, e)}
case "SBI" => asmExpression.map { e => (SBC, OneRegister(IMM_8), None, e)}
case "ANI" => asmExpression.map { e => (AND, OneRegister(IMM_8), None, e)}
case "ORI" => asmExpression.map { e => (OR, OneRegister(IMM_8), None, e)}
case "XRI" => asmExpression.map { e => (XOR, OneRegister(IMM_8), None, e)}
case "CPI" => asmExpression.map { e => (CP, OneRegister(IMM_8), None, e)}
case "SHLD" => asmExpression.map { e => (LD_16, TwoRegisters(MEM_ABS_16, HL), None, e)}
case "LHLD" => asmExpression.map { e => (LD_16, TwoRegisters(HL, MEM_ABS_16), None, e)}
case "STA" => asmExpression.map { e => (LD, TwoRegisters(MEM_ABS_8, A), None, e)}
case "LDA" => asmExpression.map { e => (LD, TwoRegisters(A, MEM_ABS_8), None, e)}
case "RST" => asmExpression.map {
case LiteralExpression(value, _) if value >=0 && value <= 7=> (RST, NoRegisters, None, LiteralExpression(value * 8, 1))
case _ =>
log.error("Invalid parameter for RST", Some(pos))
(NOP, NoRegisters, None, zero)
}
case "IN" => asmExpression.map { e => (IN_IMM, OneRegister(A), None, e) }
case "OUT" => asmExpression.map { e => (OUT_IMM, OneRegister(A), None, e) }
case "JMP" => asmExpression.map { e => (JP, NoRegisters, None, e)}
case "JC" => asmExpression.map { e => (JP, IfFlagSet(ZFlag.C), None, e)}
case "JZ" => asmExpression.map { e => (JP, IfFlagSet(ZFlag.Z), None, e)}
case "JM" => asmExpression.map { e => (JP, IfFlagSet(ZFlag.S), None, e)}
case "JPE" => asmExpression.map { e => (JP, IfFlagSet(ZFlag.P), None, e)}
case "JNC" => asmExpression.map { e => (JP, IfFlagClear(ZFlag.C), None, e)}
case "JNZ" => asmExpression.map { e => (JP, IfFlagClear(ZFlag.Z), None, e)}
case "JP" => asmExpression.map { e => (JP, IfFlagClear(ZFlag.S), None, e)}
case "JPO" => asmExpression.map { e => (JP, IfFlagClear(ZFlag.P), None, e)}
case "RET" => imm(RET)
case "RC" => P("").map { _ => (RET, IfFlagSet(ZFlag.C), None, zero)}
case "RZ" => P("").map { _ => (RET, IfFlagSet(ZFlag.Z), None, zero)}
case "RM" => P("").map { _ => (RET, IfFlagSet(ZFlag.S), None, zero)}
case "RPE" => P("").map { _ => (RET, IfFlagSet(ZFlag.P), None, zero)}
case "RNC" => P("").map { _ => (RET, IfFlagClear(ZFlag.C), None, zero)}
case "RNZ" => P("").map { _ => (RET, IfFlagClear(ZFlag.Z), None, zero)}
case "RP" => P("").map { _ => (RET, IfFlagClear(ZFlag.S), None, zero)}
case "RPO" => P("").map { _ => (RET, IfFlagClear(ZFlag.P), None, zero)}
case "CALL" => asmExpression.map { e => (CALL, NoRegisters, None, e)}
case "CC" => asmExpression.map { e => (CALL, IfFlagSet(ZFlag.C), None, e)}
case "CZ" => asmExpression.map { e => (CALL, IfFlagSet(ZFlag.Z), None, e)}
case "CM" => asmExpression.map { e => (CALL, IfFlagSet(ZFlag.S), None, e)}
case "CPE" => asmExpression.map { e => (CALL, IfFlagSet(ZFlag.P), None, e)}
case "CNC" => asmExpression.map { e => (CALL, IfFlagClear(ZFlag.C), None, e)}
case "CNZ" => asmExpression.map { e => (CALL, IfFlagClear(ZFlag.Z), None, e)}
case "CP" => asmExpression.map { e => (CALL, IfFlagClear(ZFlag.S), None, e)}
case "CPO" => asmExpression.map { e => (CALL, IfFlagClear(ZFlag.P), None, e)}
case _ =>
log.error("Unsupported opcode " + opcode, Some(pos))
imm(NOP)
}
} yield {
val (actualOpcode, registers, offset, param) = tuple4
Z80AssemblyStatement(actualOpcode, registers, offset, param, el)
}
}
val asmInstruction: P[ExecutableStatement] = if (useIntelSyntax) intelAsmInstruction else zilogAsmInstruction

View File

@ -8,9 +8,10 @@ import org.scalatest.{FunSuite, Matchers}
*/
class Z80AssemblySuite extends FunSuite with Matchers {
test("Common I80 instructions") {
test("Common I80 instructions (Zilog syntax)") {
EmuUnoptimizedIntel8080Run(
"""
| #pragma zilog_syntax
| asm void main () {
| ret
|
@ -58,19 +59,19 @@ class Z80AssemblySuite extends FunSuite with Matchers {
| ld l,$2e
| cpl
|
| ld hl,$2121
| ld sp,$3131
| ld ($fffe),a
| inc sp
| inc (hl)
| dec (hl)
| ld h,$26
| ld (hl),$36
| scf
| add hl,sp
| ld a,($fffe)
| dec sp
| inc a
| dec a
| ld l,$2e
| ld a,$3e
| ccf
|
| ld b,b
@ -273,10 +274,277 @@ class Z80AssemblySuite extends FunSuite with Matchers {
| }
""".stripMargin)
}
test("Intel 8080 instructions") {
test("Common I80 instructions (Intel syntax)") {
EmuUnoptimizedIntel8080Run(
"""
| #pragma intel_syntax
| asm void main () {
| ret
|
| nop
| lxi b, 0101h
| stax b
| inx b
| inr b
| dcr b
| mvi b,6
| rlc
| dad b
| ldax b
| dcx b
| inr c
| dcr c
| mvi c,0eh
| rrc
|
| lxi d,1111h
| stax d
| inx d
| inr d
| dcr d
| mvi d,16h
| ral
| dad d
| ldax d
| dcx d
| inr d
| dcr d
| mvi e, 1eh
| rar
|
| lxi h, 2121h
| inx h
| inr h
| dcr h
| mvi h,26h
| daa
| dad h
| dcx h
| inr l
| dcr l
| mvi l,2eh
| cma
|
| lxi sp, 3131h
| sta 0fffeh
| inx sp
| inr m
| dcr m
| mvi m, 36h
| stc
| dad sp
| lda 0fffeh
| dcx sp
| inr a
| dcr a
| mvi a, 3eh
| cmc
|
| mov b,b
| mov b,c
| mov b,d
| mov b,e
| mov b,h
| mov b,l
| mov b,m
| mov b,a
|
| mov c,b
| mov c,c
| mov c,d
| mov c,e
| mov c,h
| mov c,l
| mov c,m
| mov c,a
|
| mov d,b
| mov d,c
| mov d,d
| mov d,e
| mov d,h
| mov d,l
| mov d,m
| mov d,a
|
| mov e,b
| mov e,c
| mov e,d
| mov e,e
| mov e,h
| mov e,l
| mov e,m
| mov e,a
|
| mov h,b
| mov h,c
| mov h,d
| mov h,e
| mov h,h
| mov h,l
| mov h,m
| mov h,a
|
| mov l,b
| mov l,c
| mov l,d
| mov l,e
| mov l,h
| mov l,l
| mov l,m
| mov l,a
|
| mov m,b
| mov m,c
| mov m,d
| mov m,e
| mov m,h
| mov m,l
| hlt
| mov m,a
|
| mov a,b
| mov a,c
| mov a,d
| mov a,e
| mov a,h
| mov a,l
| mov a,m
| mov a,a
|
| add b
| add c
| add d
| add e
| add h
| add l
| add m
| add a
|
| adc b
| adc c
| adc d
| adc e
| adc h
| adc l
| adc m
| adc a
|
| sub b
| sub c
| sub d
| sub e
| sub h
| sub l
| sub m
| sub a
|
| sbb b
| sbb c
| sbb d
| sbb e
| sbb h
| sbb l
| sbb m
| sbb a
|
| ana b
| ana c
| ana d
| ana e
| ana h
| ana l
| ana m
| ana a
|
| xra b
| xra c
| xra d
| xra e
| xra h
| xra l
| xra m
| xra a
|
| ora b
| ora c
| ora d
| ora e
| ora h
| ora l
| ora m
| ora a
|
| cmp b
| cmp c
| cmp d
| cmp e
| cmp h
| cmp l
| cmp m
| cmp a
|
| rnz
| pop b
| jnz main
| jmp main
| cnz main
| push b
| adi 1
| rst 0
|
| rz
| ret
| jz main
| cz main
| call main
| aci 1
| rst 1
|
| rnc
| pop d
| jnc main
| cnc main
| push d
| sui 1
| rst 2
|
| rc
| jc main
| cc main
| sbi 1
| rst 3
|
| pop h
| xthl
| push h
| ani 1
| rst 4
|
| pchl
| xri 1
| rst 5
|
| pop psw
| di
| push psw
| ori 1
| rst 6
|
| sphl
| ei
| cpi 1
| rst 7
|
| ret
| }
""".stripMargin)
}
test("Intel 8080 instructions (Zilog syntax)") {
EmuUnoptimizedIntel8080Run(
"""
| #pragma zilog_syntax
| asm void main () {
| ret
| ld ($fffe),hl
@ -302,6 +570,35 @@ class Z80AssemblySuite extends FunSuite with Matchers {
""".stripMargin)
}
test("Intel 8080 instructions (Intel syntax)") {
EmuUnoptimizedIntel8080Run(
"""
| #pragma intel_syntax
| asm void main () {
| ret
| shld 0fffeh
| lhld 0fffeh
| out 1
| in 1
| rpo
| jpo main
| cpo main
| rpe
| jpe main
| xchg
| cpe main
| rp
| jp main
| cp main
| rm
| jm main
| cm main
|
| ret
| }
""".stripMargin)
}
test("Extended I80 instructions") {
EmuUnoptimizedZ80Run(
"""

View File

@ -47,8 +47,8 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
if (!source.contains("_panic")) effectiveSource += "\n void _panic(){while(true){}}"
log.setSource(Some(effectiveSource.lines.toIndexedSeq))
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))
// tests use Intel syntax only when forced to:
val parserF = Z80Parser("", preprocessedSource, "", options, features, pragmas.contains("intel_syntax"))
parserF.toAst match {
case Success(unoptimized, _) =>
log.assertNoErrors("Parse failed")
@ -158,7 +158,7 @@ class EmuZ80Run(cpu: millfork.Cpu.Value, nodeOptimizations: List[NodeOptimizatio
println(f.extra.toString)
println(f.lastParser.toString)
log.error("Syntax error", Some(parserF.lastPosition))
???
fail("Parsing error")
}
}