1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-12-23 08:29:35 +00:00

Dijkstra wept

This commit is contained in:
Karol Stasiak 2019-07-15 02:06:23 +02:00
parent af58b16e66
commit f3dcfc78ba
10 changed files with 164 additions and 1 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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">&quot;sta &quot; &quot;lda &quot; &quot;jmp &quot; &quot;bit &quot; &quot;eor &quot; &quot;adc &quot; &quot;sbc &quot; &quot;ora &quot; &quot;and &quot; &quot;ldx &quot; &quot;ldy &quot; &quot;stx &quot; &quot;sty &quot; &quot;tax&quot; &quot;tay&quot; &quot;tya&quot; &quot;txa&quot; &quot;txs&quot; &quot;tsx&quot; &quot;sei&quot; &quot;cli&quot; &quot;clv&quot; &quot;clc&quot; &quot;cld&quot; &quot;sed&quot; &quot;sec&quot; &quot;bra &quot; &quot;beq &quot; &quot;bne &quot; &quot;bmi &quot; &quot;bpl &quot; &quot;bcc &quot; &quot;bcs &quot; &quot;bvs &quot; bvc &quot; &quot;jsr &quot; rts&quot; &quot;rti&quot; &quot;brk&quot; &quot;rol&quot; &quot;ror&quot; &quot;asl&quot; &quot;lsr&quot; &quot;inc &quot; &quot;dec &quot; &quot;cmp &quot; &quot;cpx &quot; &quot;cpy &quot; inx iny dex dey pla pha plp hp phx plx phy ply &quot;stz &quot; &quot;ldz &quot; tza taz &quot;tsb &quot; &quot;trb &quot; ra txy tyx pld plb phb phd phk xce&#x000D;&#x000A;&#x000D;&#x000A;&quot;STA &quot; &quot;LDA &quot; &quot;JMP &quot; &quot;BIT &quot; &quot;EOR &quot; &quot;ADC &quot; &quot;SBC &quot; &quot;ORA &quot; &quot;AND &quot; &quot;LDX &quot; &quot;LDY &quot; &quot;STX &quot; &quot;STY &quot; &quot;TAX&quot; &quot;TAY&quot; &quot;TYA&quot; &quot;TXA&quot; &quot;TXS&quot; &quot;TSX&quot; &quot;SEI&quot; &quot;CLI&quot; &quot;CLV&quot; &quot;CLC&quot; &quot;CLD&quot; &quot;SED&quot; &quot;SEC&quot; &quot;BEQ &quot; &quot;BRA &quot; &quot;BNE &quot; &quot;BMI &quot; &quot;BPL &quot; &quot;BCC &quot; &quot;BCS &quot; &quot;BVS &quot; BVC &quot; &quot;JSR &quot; RTS&quot; &quot;RTI&quot; &quot;BRK&quot; &quot;ROL&quot; &quot;ROR&quot; &quot;ASL&quot; &quot;LSR&quot; &quot;INC &quot; &quot;DEC &quot; &quot;CMP &quot; &quot;CPX &quot; &quot;CPY &quot; INX INY DEX DEY PLA PHA PLP HP PHX PLX PHY PLY &quot;STZ &quot; &quot;LDZ &quot; TZA TAZ &quot;TSB &quot; &quot;TRB &quot; RA TXY TYX PLD PLB PHB PHD PHK XCE</Keywords>
<Keywords name="Keywords5">&quot;sbx &quot; &quot;isc &quot; &quot;dcp &quot; &quot;lax &quot; &quot;sax &quot; &quot;anc &quot; &quot;alr &quot; &quot;arr &quot; &quot;rra &quot; &quot;rla &quot; &quot;lxa &quot; &quot;ane &quot; &quot;xaa &quot;&#x000D;&#x000A;&quot;SBX &quot; &quot;ISC &quot; &quot;DCP &quot; &quot;LAX &quot; &quot;SAX &quot; &quot;ANC &quot; &quot;ALR &quot; &quot;ARR &quot; &quot;RRA &quot; &quot;RLA &quot; &quot;LXA &quot; &quot;ANE &quot; &quot;XAA &quot;</Keywords>

View File

@ -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 =>

View File

@ -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)

View File

@ -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

View File

@ -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)
}

View File

@ -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

View 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)
}
}
}