1
0
mirror of https://github.com/KarolS/millfork.git synced 2024-05-31 18:41:30 +00:00

Support for expressions in file() (fixes #114)

This commit is contained in:
Karol Stasiak 2021-06-21 14:18:17 +02:00
parent 307ad90ecf
commit 73beafd65e
5 changed files with 67 additions and 25 deletions

View File

@ -1749,6 +1749,38 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa
def extractArrayContents(contents1: ArrayContents): List[Expression] = contents1 match {
case LiteralContents(xs) => xs
case FileChunkContents(filePath, startE, lengthE) =>
val data = Files.readAllBytes(filePath)
val p = contents1.position
val slice = (eval(startE).map(_.quickSimplify), lengthE.map(l => eval(l).map(_.quickSimplify))) match {
case (Some(NumericConstant(start, _)), Some(Some(NumericConstant(length, _)))) =>
if (data.length < start) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset $start", p)
Array.fill(length.toInt)(0.toByte)
} else if (data.length < start + length) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset plus length ${start + length}", p)
Array.fill(length.toInt)(0.toByte)
} else {
data.slice(start.toInt, start.toInt + length.toInt)
}
case (Some(NumericConstant(start, _)), None) =>
if (data.length < start) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset $start", p)
Array[Byte](0)
} else {
data.drop(start.toInt)
}
case (None, Some(Some(_))) =>
log.error(s"Start offset is not a constant", p)
Array[Byte](0)
case (_, Some(None)) =>
log.error(s"Length is not a constant", p)
Array[Byte](0)
case (None, Some(None)) =>
log.error(s"Start offset and length are not constants", p)
Array[Byte](0)
}
slice.map(c => LiteralExpression(c & 0xff, 1)).toList
case CombinedContents(xs) => xs.flatMap(extractArrayContents)
case pc@ProcessedContents("struct", xs: CombinedContents) =>
checkIfArrayContentsAreSimple(xs)

View File

@ -483,6 +483,21 @@ trait ArrayContents extends Node {
def replaceVariable(variableToReplace: String, expression: Expression): ArrayContents
}
case class FileChunkContents(filePath: Path, start: Expression, length: Option[Expression]) extends ArrayContents {
def getAllExpressions(bigEndian: Boolean): List[Expression] = length match {
case Some(l) => List(start, l)
case None => List(start)
}
def renameVariable(variableToRename: String, newVariable: String): ArrayContents =
FileChunkContents(filePath,
start.renameVariable(variableToRename, newVariable),
length.map(_.renameVariable(variableToRename, newVariable)))
def replaceVariable(variableToReplace: String, expression: Expression): ArrayContents =
FileChunkContents(filePath,
start.replaceVariable(variableToReplace, expression),
length.map(_.replaceVariable(variableToReplace, expression)))
}
case class LiteralContents(contents: List[Expression]) extends ArrayContents {
override def getAllExpressions(bigEndian: Boolean): List[Expression] = contents

View File

@ -340,35 +340,13 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri
p <- "file" ~ HWS ~/ "(" ~/ HWS ~/ position("file name")
filePath <- doubleQuotedString ~/ HWS
_ <- position("file start")
optStart <- ("," ~/ HWS ~/ literalAtom ~/ HWS ~/ Pass).?
optStart <- ("," ~/ HWS ~/ mfExpression(nonStatementLevel, false) ~/ HWS ~/ Pass).?
_ <- position("slice length")
optLength <- ("," ~/ HWS ~/ literalAtom ~/ HWS ~/ Pass).?
optLength <- ("," ~/ HWS ~/ mfExpression(nonStatementLevel, false) ~/ HWS ~/ Pass).?
_ <- position("closing parentheses")
_ <- ")" ~/ Pass
} yield {
val data = Files.readAllBytes(Paths.get(currentDirectory, filePath))
val slice: Array[Byte] = (optStart.map(_.value.toInt), optLength.map(_.value.toInt)) match {
case (Some(start), Some(length)) =>
if (data.length < start) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset $start", Some(p))
Array.fill(length)(0.toByte)
} else if (data.length < start + length) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset plus length ${start + length}", Some(p))
Array.fill(length)(0.toByte)
} else {
data.slice(start, start + length)
}
case (Some(start), None) =>
if (data.length < start) {
log.error(s"File $filePath is shorter (${data.length} B) that the start offset $start", Some(p))
Array[Byte](0)
} else {
data.drop(start)
}
case (None, None) => data
case _ => throw new IllegalStateException("error parsing file()")
}
LiteralContents(slice.map(c => LiteralExpression(c & 0xff, 1)).toList)
FileChunkContents(Paths.get(currentDirectory, filePath), optStart.getOrElse(LiteralExpression(0, 1)), optLength)
}
def arrayStringContents: P[ArrayContents] = textLiteral.map(LiteralContents)

View File

@ -0,0 +1 @@
AAAAAAAAA123456789

View File

@ -698,4 +698,20 @@ class ArraySuite extends FunSuite with Matchers with AppendedClues {
m.readWord(0xc022) should equal(2)
}
}
test("Arrays from file") {
EmuUnoptimizedCrossPlatformRun(Cpu.Mos)(
"""
| const byte nine = 9
| array dummy @$4000 = file("src/test/resources/binarydata", nine, 3*3)
|
| void main () {
|
| }
| noinline byte f(byte x) = x
""".stripMargin) { m =>
m.readByte(0x4000) should equal('1'.toInt)
m.readByte(0x4008) should equal('9'.toInt)
}
}
}