mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-10 20:29:35 +00:00
Dijkstra wept
This commit is contained in:
parent
af58b16e66
commit
f3dcfc78ba
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
## Current version
|
## Current version
|
||||||
|
|
||||||
|
* Added goto.
|
||||||
|
|
||||||
* Added arrays of elements of size greater than byte.
|
* Added arrays of elements of size greater than byte.
|
||||||
|
|
||||||
* Improved passing of register parameters to assembly functions.
|
* 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
|
* 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.
|
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.
|
* `<comma separated expressions>` – traverse every value in the list, in the given order.
|
||||||
Values do not have to be constant.
|
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.
|
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
|
### `break` and `continue` statements
|
||||||
|
|
||||||
@ -345,6 +346,31 @@ continue while
|
|||||||
continue do
|
continue do
|
||||||
continue <variable>
|
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
|
### `asm` statements
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
<Keywords name="Folders in comment, middle"></Keywords>
|
<Keywords name="Folders in comment, middle"></Keywords>
|
||||||
<Keywords name="Folders in comment, close"></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="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="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="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>
|
<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
|
}) -> 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 =>
|
case s: IfStatement =>
|
||||||
compileIfStatement(ctx, s)
|
compileIfStatement(ctx, s)
|
||||||
case s: WhileStatement =>
|
case s: WhileStatement =>
|
||||||
|
@ -83,6 +83,14 @@ object Z80StatementCompiler extends AbstractStatementCompiler[ZLine] {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}) -> Nil
|
}) -> 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) =>
|
case Assignment(destination, source) =>
|
||||||
val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, source)
|
val sourceType = AbstractExpressionCompiler.getExpressionType(ctx, source)
|
||||||
val targetType = AbstractExpressionCompiler.getExpressionType(ctx, destination)
|
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 things: mutable.Map[String, Thing] = mutable.Map()
|
||||||
val pointiesUsed: mutable.Map[String, Set[String]] = mutable.Map()
|
val pointiesUsed: mutable.Map[String, Set[String]] = mutable.Map()
|
||||||
val removedThings: mutable.Set[String] = mutable.Set()
|
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 = {
|
private def addThing(t: Thing, position: Option[Position]): Unit = {
|
||||||
if (assertNotDefined(t.name, position)) {
|
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))
|
val pointies = collectPointies(stmt.statements.getOrElse(Seq.empty))
|
||||||
pointiesUsed(stmt.name) = pointies
|
pointiesUsed(stmt.name) = pointies
|
||||||
val w = get[Type]("word")
|
val w = get[Type]("word")
|
||||||
|
val p = get[Type]("pointer")
|
||||||
val name = stmt.name
|
val name = stmt.name
|
||||||
val resultType = get[Type](stmt.resultType)
|
val resultType = get[Type](stmt.resultType)
|
||||||
if (stmt.name == "main") {
|
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 a: ArrayDeclarationStatement => env.registerArray(a, options)
|
||||||
case _ => ()
|
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 {
|
val executableStatements = statements.flatMap {
|
||||||
case e: ExecutableStatement => Some(e)
|
case e: ExecutableStatement => Some(e)
|
||||||
case _ => None
|
case _ => None
|
||||||
|
@ -467,6 +467,14 @@ case class ReturnStatement(value: Option[Expression]) extends ExecutableStatemen
|
|||||||
override def getAllExpressions: List[Expression] = value.toList
|
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 {
|
case class EmptyStatement(toTypecheck: List[ExecutableStatement]) extends ExecutableStatement {
|
||||||
override def getAllExpressions: List[Expression] = toTypecheck.flatMap(_.getAllExpressions)
|
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(
|
def keywordStatement: P[Seq[ExecutableStatement]] = P(
|
||||||
returnOrDispatchStatement |
|
returnOrDispatchStatement |
|
||||||
|
gotoStatement |
|
||||||
|
labelStatement |
|
||||||
ifStatement |
|
ifStatement |
|
||||||
whileStatement |
|
whileStatement |
|
||||||
forStatement |
|
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 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 {
|
def ifStatement: P[Seq[ExecutableStatement]] = for {
|
||||||
condition <- "if" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel, false)
|
condition <- "if" ~ !letterOrDigit ~/ HWS ~/ mfExpression(nonStatementLevel, false)
|
||||||
thenBranch <- AWS ~/ executableStatements
|
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…
x
Reference in New Issue
Block a user