mirror of
https://github.com/KarolS/millfork.git
synced 2024-12-23 08:29:35 +00:00
Dijkstra wept
This commit is contained in:
parent
af58b16e66
commit
f3dcfc78ba
@ -2,6 +2,8 @@
|
||||
|
||||
## Current version
|
||||
|
||||
* Added goto.
|
||||
|
||||
* Added arrays of elements of size greater than byte.
|
||||
|
||||
* Improved passing of register parameters to assembly functions.
|
||||
|
@ -39,4 +39,6 @@ Currently, such functions may be evaluated either once or twice. This might be f
|
||||
|
||||
* when using modifying operators: calling functions on the right-hand-side index expression than modify any of the variables used on the left hand side
|
||||
|
||||
* jumping across the scope of for loop that uses a fixed list or across functions
|
||||
|
||||
The above list is not exhaustive.
|
||||
|
@ -328,6 +328,7 @@ for <variable> : [ <comma separated expressions> ] {
|
||||
* `<comma separated expressions>` – traverse every value in the list, in the given order.
|
||||
Values do not have to be constant.
|
||||
If a value is not a constant and its value changes while executing the loop, the behaviour is undefined.
|
||||
Jumps using `goto` across the scope of this kind of loop are disallowed.
|
||||
|
||||
### `break` and `continue` statements
|
||||
|
||||
@ -345,6 +346,31 @@ continue while
|
||||
continue do
|
||||
continue <variable>
|
||||
```
|
||||
|
||||
### `goto` and `label`
|
||||
|
||||
Syntax:
|
||||
|
||||
```
|
||||
goto <expression>
|
||||
label <name>
|
||||
```
|
||||
|
||||
The `label` statement defines a constant pointer that refers to the current position in the code.
|
||||
Such labels are only visible in the scope of the local function.
|
||||
|
||||
The `goto` expression jumps to the pointer value of the expression.
|
||||
|
||||
Jumping using `goto` across the scope of for loop that uses a fixed list or across functions is not allowed.
|
||||
|
||||
Computed gotos are supported:
|
||||
|
||||
```
|
||||
pointer p
|
||||
p = x
|
||||
goto p
|
||||
label x
|
||||
```
|
||||
|
||||
### `asm` statements
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
||||
<Keywords name="Folders in comment, middle"></Keywords>
|
||||
<Keywords name="Folders in comment, close"></Keywords>
|
||||
<Keywords name="Keywords1">void byte sbyte ubyte array word farword pointer farpointer long word_be word_le long_be long_le file int8 int16 int24 int32 int40 int48 int56 int64 signed8 fast</Keywords>
|
||||
<Keywords name="Keywords2">if else for return while do asm extern import segment break continue default alias enum struct union</Keywords>
|
||||
<Keywords name="Keywords2">if else for return while do asm extern import segment break continue default alias enum struct union goto label</Keywords>
|
||||
<Keywords name="Keywords3">defaultz petscii ascii scr petscr pet atascii atari bbc sinclair apple2 jis jisx iso_de iso_yu iso_no iso_dk iso_se iso_fi petsciiz asciiz scrz petscrz petz atasciiz atariz bbcz sinclairz apple2z jisz jisxz iso_dez iso_yuz iso_noz iso_dkz iso_sez iso_fiz until to downto parallelto static stack ref const volatile paralleluntil inline noinline macro register kernal_interrupt interrupt reentrant hi lo sin cos tan nonet align false true nullptr</Keywords>
|
||||
<Keywords name="Keywords4">"sta " "lda " "jmp " "bit " "eor " "adc " "sbc " "ora " "and " "ldx " "ldy " "stx " "sty " "tax" "tay" "tya" "txa" "txs" "tsx" "sei" "cli" "clv" "clc" "cld" "sed" "sec" "bra " "beq " "bne " "bmi " "bpl " "bcc " "bcs " "bvs " bvc " "jsr " rts" "rti" "brk" "rol" "ror" "asl" "lsr" "inc " "dec " "cmp " "cpx " "cpy " inx iny dex dey pla pha plp hp phx plx phy ply "stz " "ldz " tza taz "tsb " "trb " ra txy tyx pld plb phb phd phk xce

"STA " "LDA " "JMP " "BIT " "EOR " "ADC " "SBC " "ORA " "AND " "LDX " "LDY " "STX " "STY " "TAX" "TAY" "TYA" "TXA" "TXS" "TSX" "SEI" "CLI" "CLV" "CLC" "CLD" "SED" "SEC" "BEQ " "BRA " "BNE " "BMI " "BPL " "BCC " "BCS " "BVS " BVC " "JSR " RTS" "RTI" "BRK" "ROL" "ROR" "ASL" "LSR" "INC " "DEC " "CMP " "CPX " "CPY " INX INY DEX DEY PLA PHA PLP HP PHX PLX PHY PLY "STZ " "LDZ " TZA TAZ "TSB " "TRB " RA TXY TYX PLD PLB PHB PHD PHK XCE</Keywords>
|
||||
<Keywords name="Keywords5">"sbx " "isc " "dcp " "lax " "sax " "anc " "alr " "arr " "rra " "rla " "lxa " "ane " "xaa "
"SBX " "ISC " "DCP " "LAX " "SAX " "ANC " "ALR " "ARR " "RRA " "RLA " "LXA " "ANE " "XAA "</Keywords>
|
||||
|
@ -277,6 +277,14 @@ object MosStatementCompiler extends AbstractStatementCompiler[AssemblyLine] {
|
||||
}
|
||||
}
|
||||
}) -> Nil
|
||||
case s: GotoStatement =>
|
||||
env.eval(s.target) match {
|
||||
case Some(e) => List(AssemblyLine.absolute(JMP, e)) -> Nil
|
||||
case None =>
|
||||
MosExpressionCompiler.compileToZReg(ctx, s.target) ++ List(AssemblyLine(JMP, Indirect, env.get[ThingInMemory]("__reg.loword").toAddress)) -> Nil
|
||||
}
|
||||
case s: LabelStatement =>
|
||||
List(AssemblyLine.label(env.prefix + s.name)) -> Nil
|
||||
case s: IfStatement =>
|
||||
compileIfStatement(ctx, s)
|
||||
case s: WhileStatement =>
|
||||
|
@ -83,6 +83,14 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
|
||||
|
||||
}
|
||||
}) -> Nil
|
||||
case s: GotoStatement =>
|
||||
env.eval(s.target) match {
|
||||
case Some(e) => List(ZLine(JP, NoRegisters, e)) -> Nil
|
||||
case None =>
|
||||
Z80ExpressionCompiler.compileToHL(ctx, s.target) ++ List(ZLine(JP, OneRegister(ZRegister.HL), Constant.Zero)) -> Nil
|
||||
}
|
||||
case s: LabelStatement =>
|
||||
List(ZLine.label(env.prefix + s.name)) -> Nil
|
||||
case Assignment(destination, source) =>
|
||||
val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, source)
|
||||
val targetType = AbstractExpressionCompiler.getExpressionType(ctx, destination)
|
||||
|
46
src/main/scala/millfork/env/Environment.scala
vendored
46
src/main/scala/millfork/env/Environment.scala
vendored
@ -210,6 +210,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
val things: mutable.Map[String, Thing] = mutable.Map()
|
||||
val pointiesUsed: mutable.Map[String, Set[String]] = mutable.Map()
|
||||
val removedThings: mutable.Set[String] = mutable.Set()
|
||||
val knownLocalLabels: mutable.Set[(String, Option[Position])] = mutable.Set()
|
||||
|
||||
private def addThing(t: Thing, position: Option[Position]): Unit = {
|
||||
if (assertNotDefined(t.name, position)) {
|
||||
@ -938,6 +939,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
val pointies = collectPointies(stmt.statements.getOrElse(Seq.empty))
|
||||
pointiesUsed(stmt.name) = pointies
|
||||
val w = get[Type]("word")
|
||||
val p = get[Type]("pointer")
|
||||
val name = stmt.name
|
||||
val resultType = get[Type](stmt.resultType)
|
||||
if (stmt.name == "main") {
|
||||
@ -1022,6 +1024,50 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
case a: ArrayDeclarationStatement => env.registerArray(a, options)
|
||||
case _ => ()
|
||||
}
|
||||
def scanForLabels(statement: Statement): Unit = statement match {
|
||||
case c: CompoundStatement => c.getChildStatements.foreach(scanForLabels)
|
||||
case LabelStatement(labelName) => env.knownLocalLabels += (labelName -> statement.position)
|
||||
case _ => ()
|
||||
}
|
||||
statements.foreach(scanForLabels)
|
||||
for ((knownLabel, position) <- env.knownLocalLabels) {
|
||||
env.addThing(knownLabel, ConstantThing(env.prefix + knownLabel, Label(env.prefix + knownLabel).toAddress, p), position)
|
||||
}
|
||||
|
||||
// not all in-function gotos are allowed; warn about the provably wrong ones:
|
||||
def checkLabels(statements: Seq[Statement]) = {
|
||||
def getAllSafeLabels(statements: Seq[Statement]): Seq[String] = statements.flatMap {
|
||||
case _: ForEachStatement => Nil
|
||||
case c: CompoundStatement => getAllSafeLabels(c.getChildStatements)
|
||||
case LabelStatement(labelName) => Seq(labelName)
|
||||
case _ => Nil
|
||||
}
|
||||
def getAllSafeGotos(statements: Seq[Statement]):Seq[String] = statements.flatMap {
|
||||
case _: ForEachStatement => Nil
|
||||
case c: CompoundStatement => getAllSafeGotos(c.getChildStatements)
|
||||
case GotoStatement(VariableExpression(labelName)) if env.knownLocalLabels.exists(_._1.==(labelName)) => Seq(labelName)
|
||||
case _ => Nil
|
||||
}
|
||||
def doOnlyCheck(position: Option[Position], statements: Seq[Statement]): Unit = {
|
||||
val l = getAllSafeLabels(statements).toSet
|
||||
val g = getAllSafeGotos(statements).toSet
|
||||
val bad = g.&(env.knownLocalLabels.map(_._1)).--(l)
|
||||
if (bad.nonEmpty) {
|
||||
log.warn("Detected cross-loop gotos to labels " + bad.mkString(", "), position)
|
||||
}
|
||||
}
|
||||
def recurse(statements: Seq[Statement]):Unit = statements.foreach {
|
||||
case c: ForEachStatement =>
|
||||
doOnlyCheck(c.position, c.body)
|
||||
recurse(c.body)
|
||||
case c: CompoundStatement => recurse(c.getChildStatements)
|
||||
case _ => Nil
|
||||
}
|
||||
doOnlyCheck(stmt.position, statements)
|
||||
recurse(statements)
|
||||
}
|
||||
checkLabels(statements)
|
||||
|
||||
val executableStatements = statements.flatMap {
|
||||
case e: ExecutableStatement => Some(e)
|
||||
case _ => None
|
||||
|
@ -467,6 +467,14 @@ case class ReturnStatement(value: Option[Expression]) extends ExecutableStatemen
|
||||
override def getAllExpressions: List[Expression] = value.toList
|
||||
}
|
||||
|
||||
case class GotoStatement(target: Expression) extends ExecutableStatement {
|
||||
override def getAllExpressions: List[Expression] = List(target)
|
||||
}
|
||||
|
||||
case class LabelStatement(name: String) extends ExecutableStatement {
|
||||
override def getAllExpressions: List[Expression] = Nil
|
||||
}
|
||||
|
||||
case class EmptyStatement(toTypecheck: List[ExecutableStatement]) extends ExecutableStatement {
|
||||
override def getAllExpressions: List[Expression] = toTypecheck.flatMap(_.getAllExpressions)
|
||||
}
|
||||
|
@ -380,6 +380,8 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
|
||||
|
||||
def keywordStatement: P[Seq[ExecutableStatement]] = P(
|
||||
returnOrDispatchStatement |
|
||||
gotoStatement |
|
||||
labelStatement |
|
||||
ifStatement |
|
||||
whileStatement |
|
||||
forStatement |
|
||||
@ -437,6 +439,10 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
|
||||
|
||||
val returnOrDispatchStatement: P[Seq[ExecutableStatement]] = "return" ~ !letterOrDigit ~/ HWS ~ (dispatchStatementBody | mfExpression(nonStatementLevel, false).?.map(ReturnStatement).map(Seq(_)))
|
||||
|
||||
val gotoStatement: P[Seq[ExecutableStatement]] = "goto" ~ !letterOrDigit ~/ HWS ~ mfExpression(nonStatementLevel, false).map(GotoStatement).map(Seq(_))
|
||||
|
||||
val labelStatement: P[Seq[ExecutableStatement]] = "label" ~ !letterOrDigit ~/ HWS ~ identifier.map(LabelStatement).map(Seq(_))
|
||||
|
||||
def ifStatement: P[Seq[ExecutableStatement]] = for {
|
||||
condition <- "if" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel, false)
|
||||
thenBranch <- AWS ~/ executableStatements
|
||||
|
57
src/test/scala/millfork/test/GotoSuite.scala
Normal file
57
src/test/scala/millfork/test/GotoSuite.scala
Normal file
@ -0,0 +1,57 @@
|
||||
package millfork.test
|
||||
|
||||
import millfork.Cpu
|
||||
import millfork.test.emu.{EmuCrossPlatformBenchmarkRun, EmuUnoptimizedCrossPlatformRun}
|
||||
import org.scalatest.{FunSuite, Matchers}
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
class GotoSuite extends FunSuite with Matchers {
|
||||
|
||||
test("Goto 1") {
|
||||
EmuCrossPlatformBenchmarkRun(Cpu.Mos, Cpu.Cmos, Cpu.Z80, Cpu.Intel8080, Cpu.Sharp)(
|
||||
"""
|
||||
|
|
||||
| byte output @$c000
|
||||
|
|
||||
| void main() {
|
||||
| byte x
|
||||
| x = 0
|
||||
| if x == 0 {
|
||||
| label a
|
||||
| }
|
||||
| x += 1
|
||||
| if x < 100 {
|
||||
| goto a
|
||||
| }
|
||||
| output = x
|
||||
| }
|
||||
|
|
||||
""".stripMargin){m =>
|
||||
m.readByte(0xc000) should equal(100)
|
||||
}
|
||||
}
|
||||
|
||||
test("Cross-loop goto") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.Mos, Cpu.Z80)(
|
||||
"""
|
||||
|
|
||||
| byte output @$c000
|
||||
|
|
||||
| void main() {
|
||||
| byte x
|
||||
| label a
|
||||
| for x : [1,2,3] {
|
||||
| if x == 0 { goto a }
|
||||
| goto b
|
||||
| label b
|
||||
| output = x
|
||||
| }
|
||||
| }
|
||||
|
|
||||
""".stripMargin){m =>
|
||||
m.readByte(0xc000) should equal(3)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user