mirror of
https://github.com/KarolS/millfork.git
synced 2024-12-23 23:30:22 +00:00
Preliminary struct support
This commit is contained in:
parent
4cd1db0e0f
commit
d3e62c0065
103
src/main/scala/millfork/env/Environment.scala
vendored
103
src/main/scala/millfork/env/Environment.scala
vendored
@ -11,6 +11,7 @@ import millfork.output._
|
||||
import org.apache.commons.lang3.StringUtils
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.collection.mutable.ListBuffer
|
||||
|
||||
|
||||
/**
|
||||
@ -749,6 +750,45 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
}
|
||||
}
|
||||
|
||||
def registerStruct(stmt: StructDefinitionStatement): Unit = {
|
||||
stmt.fields.foreach{ f =>
|
||||
if (Environment.invalidFieldNames.contains(f._2)) {
|
||||
log.error(s"Invalid field name: `${f._2}`", stmt.position)
|
||||
}
|
||||
}
|
||||
addThing(StructType(stmt.name, stmt.fields), stmt.position)
|
||||
}
|
||||
|
||||
def getTypeSize(name: String, path: Set[String]): Int = {
|
||||
if (path.contains(name)) return -1
|
||||
val t = get[Type](name)
|
||||
t match {
|
||||
case s: StructType =>
|
||||
if (s.mutableSize >= 0) s.mutableSize
|
||||
else {
|
||||
val newPath = path + name
|
||||
var sum = 0
|
||||
for( (fieldType, _) <- s.fields) {
|
||||
val fieldSize = getTypeSize(fieldType, newPath)
|
||||
if (fieldSize < 0) return -1
|
||||
sum += fieldSize
|
||||
}
|
||||
s.mutableSize = sum
|
||||
if (sum > 0xff) {
|
||||
log.error(s"Struct `$name` is larger than 255 bytes")
|
||||
}
|
||||
val b = get[Type]("byte")
|
||||
var offset = 0
|
||||
for( (fieldType, fieldName) <- s.fields) {
|
||||
addThing(ConstantThing(s"$name.$fieldName.offset", NumericConstant(offset, 1), b), None)
|
||||
offset += getTypeSize(fieldType, newPath)
|
||||
}
|
||||
sum
|
||||
}
|
||||
case _ => t.size
|
||||
}
|
||||
}
|
||||
|
||||
def collectPointies(stmts: Seq[Statement]): Set[String] = {
|
||||
val pointies: mutable.Set[String] = new mutable.HashSet()
|
||||
pointies ++= stmts.flatMap(_.getAllPointies)
|
||||
@ -941,7 +981,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
registerAddressConstant(v, stmt.position, options)
|
||||
val addr = v.toAddress
|
||||
for((suffix, offset, t) <- getSubvariables(typ)) {
|
||||
addThing(RelativeVariable(v.name + suffix, addr + offset, t, zeropage = zp, None, isVolatile = v.isVolatile), stmt.position)
|
||||
val subv = RelativeVariable(v.name + suffix, addr + offset, t, zeropage = zp, None, isVolatile = v.isVolatile)
|
||||
addThing(subv, stmt.position)
|
||||
registerAddressConstant(subv, stmt.position, options)
|
||||
}
|
||||
case ByMosRegister(_) => ()
|
||||
case ByZRegister(_) => ()
|
||||
@ -1218,7 +1260,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
addThing(ConstantThing(v.name + "`", addr, b), stmt.position)
|
||||
}
|
||||
for((suffix, offset, t) <- getSubvariables(typ)) {
|
||||
addThing(RelativeVariable(prefix + name + suffix, addr + offset, t, zeropage = v.zeropage, declaredBank = stmt.bank, isVolatile = v.isVolatile), stmt.position)
|
||||
val subv = RelativeVariable(prefix + name + suffix, addr + offset, t, zeropage = v.zeropage, declaredBank = stmt.bank, isVolatile = v.isVolatile)
|
||||
addThing(subv, stmt.position)
|
||||
registerAddressConstant(subv, stmt.position, options)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1231,7 +1275,11 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
return (".lo", 0, b) ::
|
||||
(".hi", 1, b) ::
|
||||
(".loword", 0, w) ::
|
||||
(".loword.lo", 0, b) ::
|
||||
(".loword.hi", 1, b) ::
|
||||
(".b2b3", 2, w) ::
|
||||
(".b2b3.lo", 2, b) ::
|
||||
(".b2b3.hi", 3, b) ::
|
||||
List.tabulate(typ.size) { i => (".b" + i, i, b) }
|
||||
}
|
||||
typ match {
|
||||
@ -1241,20 +1289,46 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
(".hi", 1, b))
|
||||
case 3 => List(
|
||||
(".loword", 0, w),
|
||||
(".loword.lo", 0, b),
|
||||
(".loword.hi", 1, b),
|
||||
(".hiword", 1, w),
|
||||
(".hiword.lo", 1, b),
|
||||
(".hiword.hi", 2, b),
|
||||
(".b0", 0, b),
|
||||
(".b1", 1, b),
|
||||
(".b2", 2, b))
|
||||
case 4 => List(
|
||||
(".loword", 0, w),
|
||||
(".hiword", 2, w),
|
||||
(".loword.lo", 0, b),
|
||||
(".loword.hi", 1, b),
|
||||
(".hiword.lo", 2, b),
|
||||
(".hiword.hi", 3, b),
|
||||
(".b0", 0, b),
|
||||
(".b1", 1, b),
|
||||
(".b2", 2, b),
|
||||
(".b3", 3, b))
|
||||
case sz if sz > 4 => (".lo", 0, b) :: (".loword", 0, w) :: List.tabulate(sz){ i => (".b" + i, i, b) }
|
||||
case sz if sz > 4 =>
|
||||
(".lo", 0, b) ::
|
||||
(".loword", 0, w) ::
|
||||
(".loword.lo", 0, b) ::
|
||||
(".loword.hi", 1, b) ::
|
||||
List.tabulate(sz){ i => (".b" + i, i, b) }
|
||||
case _ => Nil
|
||||
}
|
||||
case s: StructType =>
|
||||
val builder = new ListBuffer[(String, Int, VariableType)]
|
||||
var offset = 0
|
||||
for((typeName, fieldName) <- s.fields) {
|
||||
val typ = get[VariableType](typeName)
|
||||
val suffix = "." + fieldName
|
||||
builder += ((suffix, offset, typ))
|
||||
builder ++= getSubvariables(typ).map {
|
||||
case (innerSuffix, innerOffset, innerType) => (suffix + innerSuffix, offset + innerOffset, innerType)
|
||||
}
|
||||
offset += typ.size
|
||||
}
|
||||
builder.toList
|
||||
case _ => Nil
|
||||
}
|
||||
}
|
||||
@ -1309,6 +1383,23 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
aliasesToAdd.foreach(a => things += a.name -> a)
|
||||
}
|
||||
|
||||
def fixStructSizes(): Unit = {
|
||||
val allStructTypes = things.values.flatMap {
|
||||
case StructType(name, _) => Some(name)
|
||||
case _ => None
|
||||
}
|
||||
var iterations = allStructTypes.size
|
||||
while (iterations >= 0) {
|
||||
var ok = true
|
||||
for (t <- allStructTypes) {
|
||||
if (getTypeSize(t, Set()) < 0) ok = false
|
||||
}
|
||||
if (ok) return
|
||||
iterations -= 1
|
||||
}
|
||||
log.error("Cycles in struct definitions found")
|
||||
}
|
||||
|
||||
def collectDeclarations(program: Program, options: CompilationOptions): Unit = {
|
||||
val b = get[VariableType]("byte")
|
||||
val v = get[Type]("void")
|
||||
@ -1323,6 +1414,11 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
||||
case e: EnumDefinitionStatement => registerEnum(e)
|
||||
case _ =>
|
||||
}
|
||||
program.declarations.foreach {
|
||||
case s: StructDefinitionStatement => registerStruct(s)
|
||||
case _ =>
|
||||
}
|
||||
fixStructSizes()
|
||||
val pointies = collectPointies(program.declarations)
|
||||
pointiesUsed("") = pointies
|
||||
program.declarations.foreach {
|
||||
@ -1457,4 +1553,5 @@ object Environment {
|
||||
"for", "if", "do", "while", "else", "return", "default", "to", "until", "paralleluntil", "parallelto", "downto",
|
||||
"inline", "noinline"
|
||||
) ++ predefinedFunctions
|
||||
val invalidFieldNames: Set[String] = Set("addr")
|
||||
}
|
||||
|
6
src/main/scala/millfork/env/Thing.scala
vendored
6
src/main/scala/millfork/env/Thing.scala
vendored
@ -76,6 +76,12 @@ case class EnumType(name: String, count: Option[Int]) extends VariableType {
|
||||
override def isSigned: Boolean = false
|
||||
}
|
||||
|
||||
case class StructType(name: String, fields: List[(String, String)]) extends VariableType {
|
||||
override def size: Int = mutableSize
|
||||
var mutableSize: Int = -1
|
||||
override def isSigned: Boolean = false
|
||||
}
|
||||
|
||||
sealed trait BooleanType extends Type {
|
||||
def size = 0
|
||||
|
||||
|
@ -300,6 +300,10 @@ case class EnumDefinitionStatement(name: String, variants: List[(String, Option[
|
||||
override def getAllExpressions: List[Expression] = variants.flatMap(_._2)
|
||||
}
|
||||
|
||||
case class StructDefinitionStatement(name: String, fields: List[(String, String)]) extends DeclarationStatement {
|
||||
override def getAllExpressions: List[Expression] = Nil
|
||||
}
|
||||
|
||||
case class ArrayDeclarationStatement(name: String,
|
||||
bank: Option[String],
|
||||
length: Option[Expression],
|
||||
|
@ -472,9 +472,25 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
|
||||
variants <- enumVariants ~/ Pass
|
||||
} yield Seq(EnumDefinitionStatement(name, variants).pos(p))
|
||||
|
||||
val structField: P[(String, String)] = for {
|
||||
typ <- identifier ~/ HWS
|
||||
name <- identifier ~ HWS
|
||||
} yield typ -> name
|
||||
|
||||
val structFields: P[List[(String, String)]] =
|
||||
("{" ~/ AWS ~ structField.rep(sep = NoCut(EOLOrComma) ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList)
|
||||
|
||||
val structDefinition: P[Seq[StructDefinitionStatement]] = for {
|
||||
p <- position()
|
||||
_ <- "struct" ~ !letterOrDigit ~/ SWS ~ position("struct name")
|
||||
name <- identifier ~/ HWS
|
||||
_ <- position("struct defintion block")
|
||||
fields <- structFields ~/ Pass
|
||||
} yield Seq(StructDefinitionStatement(name, fields).pos(p))
|
||||
|
||||
val program: Parser[Program] = for {
|
||||
_ <- Start ~/ AWS ~/ Pass
|
||||
definitions <- (importStatement | arrayDefinition | aliasDefinition | enumDefinition | functionDefinition | globalVariableDefinition).rep(sep = EOL)
|
||||
definitions <- (importStatement | arrayDefinition | aliasDefinition | enumDefinition | structDefinition | functionDefinition | globalVariableDefinition).rep(sep = EOL)
|
||||
_ <- AWS ~ End
|
||||
} yield Program(definitions.flatten.toList)
|
||||
|
||||
|
71
src/test/scala/millfork/test/StructSuite.scala
Normal file
71
src/test/scala/millfork/test/StructSuite.scala
Normal file
@ -0,0 +1,71 @@
|
||||
package millfork.test
|
||||
|
||||
import millfork.Cpu
|
||||
import millfork.test.emu.EmuUnoptimizedCrossPlatformRun
|
||||
import org.scalatest.{FunSuite, Matchers}
|
||||
|
||||
/**
|
||||
* @author Karol Stasiak
|
||||
*/
|
||||
class StructSuite extends FunSuite with Matchers {
|
||||
|
||||
test("Basic struct support") {
|
||||
// TODO: 8080 has broken stack operations, fix and uncomment!
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.StrictMos, Cpu.Z80/*, Cpu.Intel8080*/)("""
|
||||
| struct point {
|
||||
| byte x
|
||||
| byte y
|
||||
| byte z
|
||||
| byte colour
|
||||
| }
|
||||
| point output @$c000
|
||||
| void main () {
|
||||
| stack point p
|
||||
| p = f()
|
||||
| output = p
|
||||
| }
|
||||
| point f() {
|
||||
| point x
|
||||
| x.x = 77
|
||||
| x.y = 88
|
||||
| x.z = 99
|
||||
| x.colour = 14
|
||||
| return x
|
||||
| }
|
||||
""".stripMargin) { m =>
|
||||
m.readByte(0xc000) should equal(77)
|
||||
m.readByte(0xc001) should equal(88)
|
||||
m.readByte(0xc002) should equal(99)
|
||||
m.readByte(0xc003) should equal(14)
|
||||
}
|
||||
}
|
||||
|
||||
test("Nested structs") {
|
||||
EmuUnoptimizedCrossPlatformRun(Cpu.StrictMos, Cpu.Intel8080)("""
|
||||
| struct inner { word x, word y }
|
||||
| struct s {
|
||||
| word w
|
||||
| byte b
|
||||
| pointer p
|
||||
| inner i
|
||||
| }
|
||||
| s output @$c000
|
||||
| void main () {
|
||||
| output.b = 5
|
||||
| output.w.hi = output.b
|
||||
| output.p = output.w.addr
|
||||
| output.p[0] = 6
|
||||
| output.i.x.lo = 55
|
||||
| output.i.x.hi = s.p.offset
|
||||
| output.i.y = 777
|
||||
| }
|
||||
""".stripMargin) { m =>
|
||||
m.readWord(0xc000) should equal(0x506)
|
||||
m.readByte(0xc002) should equal(5)
|
||||
m.readWord(0xc003) should equal(0xc000)
|
||||
m.readByte(0xc005) should equal(55)
|
||||
m.readByte(0xc006) should equal(3)
|
||||
m.readWord(0xc007) should equal(777)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user