diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 39b4e674..a90f3c7a 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -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) diff --git a/src/main/scala/millfork/node/Node.scala b/src/main/scala/millfork/node/Node.scala index a6ca137d..2f05c9be 100644 --- a/src/main/scala/millfork/node/Node.scala +++ b/src/main/scala/millfork/node/Node.scala @@ -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 diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index d2b9016d..8eaa03d2 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -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) diff --git a/src/test/resources/binarydata b/src/test/resources/binarydata new file mode 100644 index 00000000..fab36be5 --- /dev/null +++ b/src/test/resources/binarydata @@ -0,0 +1 @@ +AAAAAAAAA123456789 \ No newline at end of file diff --git a/src/test/scala/millfork/test/ArraySuite.scala b/src/test/scala/millfork/test/ArraySuite.scala index b5fb7026..bd2447dd 100644 --- a/src/test/scala/millfork/test/ArraySuite.scala +++ b/src/test/scala/millfork/test/ArraySuite.scala @@ -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) + } + } }