mirror of
https://github.com/KarolS/millfork.git
synced 2025-01-11 12:29:46 +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 org.apache.commons.lang3.StringUtils
|
||||||
|
|
||||||
import scala.collection.mutable
|
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] = {
|
def collectPointies(stmts: Seq[Statement]): Set[String] = {
|
||||||
val pointies: mutable.Set[String] = new mutable.HashSet()
|
val pointies: mutable.Set[String] = new mutable.HashSet()
|
||||||
pointies ++= stmts.flatMap(_.getAllPointies)
|
pointies ++= stmts.flatMap(_.getAllPointies)
|
||||||
@ -941,7 +981,9 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
|||||||
registerAddressConstant(v, stmt.position, options)
|
registerAddressConstant(v, stmt.position, options)
|
||||||
val addr = v.toAddress
|
val addr = v.toAddress
|
||||||
for((suffix, offset, t) <- getSubvariables(typ)) {
|
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 ByMosRegister(_) => ()
|
||||||
case ByZRegister(_) => ()
|
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)
|
addThing(ConstantThing(v.name + "`", addr, b), stmt.position)
|
||||||
}
|
}
|
||||||
for((suffix, offset, t) <- getSubvariables(typ)) {
|
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) ::
|
return (".lo", 0, b) ::
|
||||||
(".hi", 1, b) ::
|
(".hi", 1, b) ::
|
||||||
(".loword", 0, w) ::
|
(".loword", 0, w) ::
|
||||||
|
(".loword.lo", 0, b) ::
|
||||||
|
(".loword.hi", 1, b) ::
|
||||||
(".b2b3", 2, w) ::
|
(".b2b3", 2, w) ::
|
||||||
|
(".b2b3.lo", 2, b) ::
|
||||||
|
(".b2b3.hi", 3, b) ::
|
||||||
List.tabulate(typ.size) { i => (".b" + i, i, b) }
|
List.tabulate(typ.size) { i => (".b" + i, i, b) }
|
||||||
}
|
}
|
||||||
typ match {
|
typ match {
|
||||||
@ -1241,20 +1289,46 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
|||||||
(".hi", 1, b))
|
(".hi", 1, b))
|
||||||
case 3 => List(
|
case 3 => List(
|
||||||
(".loword", 0, w),
|
(".loword", 0, w),
|
||||||
|
(".loword.lo", 0, b),
|
||||||
|
(".loword.hi", 1, b),
|
||||||
(".hiword", 1, w),
|
(".hiword", 1, w),
|
||||||
|
(".hiword.lo", 1, b),
|
||||||
|
(".hiword.hi", 2, b),
|
||||||
(".b0", 0, b),
|
(".b0", 0, b),
|
||||||
(".b1", 1, b),
|
(".b1", 1, b),
|
||||||
(".b2", 2, b))
|
(".b2", 2, b))
|
||||||
case 4 => List(
|
case 4 => List(
|
||||||
(".loword", 0, w),
|
(".loword", 0, w),
|
||||||
(".hiword", 2, w),
|
(".hiword", 2, w),
|
||||||
|
(".loword.lo", 0, b),
|
||||||
|
(".loword.hi", 1, b),
|
||||||
|
(".hiword.lo", 2, b),
|
||||||
|
(".hiword.hi", 3, b),
|
||||||
(".b0", 0, b),
|
(".b0", 0, b),
|
||||||
(".b1", 1, b),
|
(".b1", 1, b),
|
||||||
(".b2", 2, b),
|
(".b2", 2, b),
|
||||||
(".b3", 3, 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 _ => 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
|
case _ => Nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1309,6 +1383,23 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
|
|||||||
aliasesToAdd.foreach(a => things += a.name -> a)
|
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 = {
|
def collectDeclarations(program: Program, options: CompilationOptions): Unit = {
|
||||||
val b = get[VariableType]("byte")
|
val b = get[VariableType]("byte")
|
||||||
val v = get[Type]("void")
|
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 e: EnumDefinitionStatement => registerEnum(e)
|
||||||
case _ =>
|
case _ =>
|
||||||
}
|
}
|
||||||
|
program.declarations.foreach {
|
||||||
|
case s: StructDefinitionStatement => registerStruct(s)
|
||||||
|
case _ =>
|
||||||
|
}
|
||||||
|
fixStructSizes()
|
||||||
val pointies = collectPointies(program.declarations)
|
val pointies = collectPointies(program.declarations)
|
||||||
pointiesUsed("") = pointies
|
pointiesUsed("") = pointies
|
||||||
program.declarations.foreach {
|
program.declarations.foreach {
|
||||||
@ -1457,4 +1553,5 @@ object Environment {
|
|||||||
"for", "if", "do", "while", "else", "return", "default", "to", "until", "paralleluntil", "parallelto", "downto",
|
"for", "if", "do", "while", "else", "return", "default", "to", "until", "paralleluntil", "parallelto", "downto",
|
||||||
"inline", "noinline"
|
"inline", "noinline"
|
||||||
) ++ predefinedFunctions
|
) ++ 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
|
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 {
|
sealed trait BooleanType extends Type {
|
||||||
def size = 0
|
def size = 0
|
||||||
|
|
||||||
|
@ -300,6 +300,10 @@ case class EnumDefinitionStatement(name: String, variants: List[(String, Option[
|
|||||||
override def getAllExpressions: List[Expression] = variants.flatMap(_._2)
|
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,
|
case class ArrayDeclarationStatement(name: String,
|
||||||
bank: Option[String],
|
bank: Option[String],
|
||||||
length: Option[Expression],
|
length: Option[Expression],
|
||||||
|
@ -472,9 +472,25 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
|
|||||||
variants <- enumVariants ~/ Pass
|
variants <- enumVariants ~/ Pass
|
||||||
} yield Seq(EnumDefinitionStatement(name, variants).pos(p))
|
} 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 {
|
val program: Parser[Program] = for {
|
||||||
_ <- Start ~/ AWS ~/ Pass
|
_ <- 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
|
_ <- AWS ~ End
|
||||||
} yield Program(definitions.flatten.toList)
|
} 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…
x
Reference in New Issue
Block a user