From 25c440f17dfc5e40c414aaa5f9f3600bbb3227a4 Mon Sep 17 00:00:00 2001 From: Karol Stasiak Date: Mon, 24 Jun 2019 22:32:29 +0200 Subject: [PATCH] Add local arrays --- CHANGELOG.md | 2 + docs/lang/syntax.md | 7 +- src/main/scala/millfork/env/Environment.scala | 64 +++++++++++-------- .../node/opt/UnusedLocalVariables.scala | 4 ++ src/main/scala/millfork/parser/MfParser.scala | 2 +- src/test/scala/millfork/test/ArraySuite.scala | 35 ++++++++++ 6 files changed, 84 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dcc9512..52f7a4d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,8 @@ * Arrays can now be constant. +* Arrays can now be local. + * Added hint for identifiers with typos. * Aliases now also support subfields. diff --git a/docs/lang/syntax.md b/docs/lang/syntax.md index fdd178f5..eec893e3 100644 --- a/docs/lang/syntax.md +++ b/docs/lang/syntax.md @@ -104,6 +104,10 @@ This allows for overriding definitions of library functions by another library: An array is a continuous sequence of bytes in memory. +An array declaration can happen at either top level of a file (*global* arrays), +or a top level of a function (*local* arrays). +Regardless of where they were declared, arrays are considered static. + Syntax: `[segment()] [const] array [()] [[]] [align ( )] [@
] [= ]` @@ -133,7 +137,8 @@ If the declared size and the size deduced from the `` don't matc * `
` is a constant expression that defines where in the memory the array is or will be located. -* `` is an array literal, see [Literals](./literals.md) +* `` is an array literal, see [Literals](./literals.md). +Local arrays can have initial values only if they're const. TODO diff --git a/src/main/scala/millfork/env/Environment.scala b/src/main/scala/millfork/env/Environment.scala index 80121b7e..7bdd075b 100644 --- a/src/main/scala/millfork/env/Environment.scala +++ b/src/main/scala/millfork/env/Environment.scala @@ -375,7 +375,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa orElse(maybeGet[ThingInMemory](arrayName)). orElse(maybeGet[ThingInMemory](arrayName + ".array")). orElse(maybeGet[ConstantThing](arrayName)). - getOrElse(log.fatal(s"`$arrayName` is not an array or a pointer")) + getOrElse{ + log.error(s"`$arrayName` is not an array or a pointer") + get[Thing]("nullptr") + } } def getPointy(name: String): Pointy = { @@ -1018,6 +1021,7 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa case Some(statements) => statements.foreach { case v: VariableDeclarationStatement => env.registerVariable(v, options, pointies(v.name)) + case a: ArrayDeclarationStatement => env.registerArray(a, options) case _ => () } val executableStatements = statements.flatMap { @@ -1293,6 +1297,10 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa if (options.flag(CompilationFlag.LUnixRelocatableCode) && stmt.alignment.exists(_.isMultiplePages)) { log.error("Invalid alignment for LUnix code", stmt.position) } + if (stmt.elements.isDefined && !stmt.const && parent.isDefined) { + log.error(s"Local array `${stmt.name}` cannot be initialized if it's not const", stmt.position) + } + val arrayName = prefix + stmt.name val b = get[VariableType]("byte") val w = get[VariableType]("word") val p = get[Type]("pointer") @@ -1328,38 +1336,38 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa if (length > 0xffff || length < 0) log.error(s"Array `${stmt.name}` has invalid length", stmt.position) val alignment = stmt.alignment.getOrElse(defaultArrayAlignment(options, length)) val array = address match { - case None => UninitializedArray(stmt.name + ".array", length.toInt, + case None => UninitializedArray(arrayName + ".array", length.toInt, declaredBank = stmt.bank, indexType, e, stmt.const, alignment) - case Some(aa) => RelativeArray(stmt.name + ".array", aa, length.toInt, + case Some(aa) => RelativeArray(arrayName + ".array", aa, length.toInt, declaredBank = stmt.bank, indexType, e, stmt.const) } addThing(array, stmt.position) - registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None, stmt.bank, alignment, isVolatile = false), stmt.position, options, Some(e)) + registerAddressConstant(UninitializedMemoryVariable(arrayName, p, VariableAllocationMethod.None, stmt.bank, alignment, isVolatile = false), stmt.position, options, Some(e)) val a = address match { case None => array.toAddress case Some(aa) => aa } - addThing(RelativeVariable(stmt.name + ".first", a, b, zeropage = false, + addThing(RelativeVariable(arrayName + ".first", a, b, zeropage = false, declaredBank = stmt.bank, isVolatile = false), stmt.position) if (options.flag(CompilationFlag.LUnixRelocatableCode)) { val b = get[Type]("byte") val w = get[Type]("word") - val relocatable = UninitializedMemoryVariable(stmt.name, w, VariableAllocationMethod.Static, None, NoAlignment, isVolatile = false) + val relocatable = UninitializedMemoryVariable(arrayName, w, VariableAllocationMethod.Static, None, NoAlignment, isVolatile = false) val addr = relocatable.toAddress addThing(relocatable, stmt.position) - addThing(RelativeVariable(stmt.name + ".addr.hi", addr + 1, b, zeropage = false, None, isVolatile = false), stmt.position) - addThing(RelativeVariable(stmt.name + ".addr.lo", addr, b, zeropage = false, None, isVolatile = false), stmt.position) - addThing(RelativeVariable(stmt.name + ".array.hi", addr + 1, b, zeropage = false, None, isVolatile = false), stmt.position) - addThing(RelativeVariable(stmt.name + ".array.lo", addr, b, zeropage = false, None, isVolatile = false), stmt.position) + addThing(RelativeVariable(arrayName + ".addr.hi", addr + 1, b, zeropage = false, None, isVolatile = false), stmt.position) + addThing(RelativeVariable(arrayName + ".addr.lo", addr, b, zeropage = false, None, isVolatile = false), stmt.position) + addThing(RelativeVariable(arrayName + ".array.hi", addr + 1, b, zeropage = false, None, isVolatile = false), stmt.position) + addThing(RelativeVariable(arrayName + ".array.lo", addr, b, zeropage = false, None, isVolatile = false), stmt.position) } else { - addThing(ConstantThing(stmt.name, a, p), stmt.position) - addThing(ConstantThing(stmt.name + ".hi", a.hiByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".lo", a.loByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".array.hi", a.hiByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".array.lo", a.loByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(arrayName, a, p), stmt.position) + addThing(ConstantThing(arrayName + ".hi", a.hiByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(arrayName + ".lo", a.loByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(arrayName + ".array.hi", a.hiByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(arrayName + ".array.lo", a.loByte.quickSimplify, b), stmt.position) } if (length < 256) { - addThing(ConstantThing(stmt.name + ".length", lengthConst, b), stmt.position) + addThing(ConstantThing(arrayName + ".length", lengthConst, b), stmt.position) } case _ => log.error(s"Array `${stmt.name}` has weird length", stmt.position) } @@ -1406,33 +1414,33 @@ class Environment(val parent: Option[Environment], val prefix: String, val cpuFa for (element <- contents) { AbstractExpressionCompiler.checkAssignmentType(this, element, e) } - val array = InitializedArray(stmt.name + ".array", address, contents, declaredBank = stmt.bank, indexType, e, readOnly = stmt.const, alignment) + val array = InitializedArray(arrayName + ".array", address, contents, declaredBank = stmt.bank, indexType, e, readOnly = stmt.const, alignment) addThing(array, stmt.position) - registerAddressConstant(UninitializedMemoryVariable(stmt.name, p, VariableAllocationMethod.None, + registerAddressConstant(UninitializedMemoryVariable(arrayName, p, VariableAllocationMethod.None, declaredBank = stmt.bank, alignment, isVolatile = false), stmt.position, options, Some(e)) val a = address match { case None => array.toAddress case Some(aa) => aa } - addThing(RelativeVariable(stmt.name + ".first", a, e, zeropage = false, + addThing(RelativeVariable(arrayName + ".first", a, e, zeropage = false, declaredBank = stmt.bank, isVolatile = false), stmt.position) if (options.flag(CompilationFlag.LUnixRelocatableCode)) { val b = get[Type]("byte") val w = get[Type]("word") - val relocatable = UninitializedMemoryVariable(stmt.name, w, VariableAllocationMethod.Static, None, NoAlignment, isVolatile = false) + val relocatable = UninitializedMemoryVariable(arrayName, w, VariableAllocationMethod.Static, None, NoAlignment, isVolatile = false) val addr = relocatable.toAddress addThing(relocatable, stmt.position) - addThing(RelativeVariable(stmt.name + ".array.hi", addr + 1, b, zeropage = false, None, isVolatile = false), stmt.position) - addThing(RelativeVariable(stmt.name + ".array.lo", addr, b, zeropage = false, None, isVolatile = false), stmt.position) + addThing(RelativeVariable(arrayName + ".array.hi", addr + 1, b, zeropage = false, None, isVolatile = false), stmt.position) + addThing(RelativeVariable(arrayName + ".array.lo", addr, b, zeropage = false, None, isVolatile = false), stmt.position) } else { - addThing(ConstantThing(stmt.name, a, p), stmt.position) - addThing(ConstantThing(stmt.name + ".hi", a.hiByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".lo", a.loByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".array.hi", a.hiByte.quickSimplify, b), stmt.position) - addThing(ConstantThing(stmt.name + ".array.lo", a.loByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(arrayName, a, p), stmt.position) + addThing(ConstantThing(arrayName + ".hi", a.hiByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(arrayName + ".lo", a.loByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(arrayName + ".array.hi", a.hiByte.quickSimplify, b), stmt.position) + addThing(ConstantThing(arrayName + ".array.lo", a.loByte.quickSimplify, b), stmt.position) } if (length < 256) { - addThing(ConstantThing(stmt.name + ".length", NumericConstant(length, 1), b), stmt.position) + addThing(ConstantThing(arrayName + ".length", NumericConstant(length, 1), b), stmt.position) } } } diff --git a/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala b/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala index 9b649f15..d2acc9c1 100644 --- a/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala +++ b/src/main/scala/millfork/node/opt/UnusedLocalVariables.scala @@ -21,6 +21,7 @@ object UnusedLocalVariables extends NodeOptimization { def getAllLocalVariables(statements: List[Statement]): List[String] = statements.flatMap { case v: VariableDeclarationStatement => List(v.name) + case v: ArrayDeclarationStatement => List(v.name) case x: IfStatement => getAllLocalVariables(x.thenBranch) ++ getAllLocalVariables(x.elseBranch) case x: WhileStatement => getAllLocalVariables(x.body) case x: DoWhileStatement => getAllLocalVariables(x.body) @@ -32,6 +33,7 @@ object UnusedLocalVariables extends NodeOptimization { case CompoundConstant(_, l, r) => getAllReadVariables(l) ++ getAllReadVariables(r) case MemoryAddressConstant(th) => List( th.name, + th.name.stripSuffix(".array"), th.name.stripSuffix(".addr"), th.name.stripSuffix(".hi"), th.name.stripSuffix(".lo"), @@ -78,6 +80,8 @@ object UnusedLocalVariables extends NodeOptimization { def removeVariables(statements: List[Statement], localsToRemove: Set[String]): List[Statement] = if (localsToRemove.isEmpty) statements else statements.flatMap { case s: VariableDeclarationStatement => if (localsToRemove(s.name)) None else Some(s) + case s: ArrayDeclarationStatement => + if (localsToRemove(s.name)) None else Some(s) case s@ExpressionStatement(FunctionCallExpression(op, VariableExpression(n) :: params)) if op.endsWith("=") => if (localsToRemove(n)) { params.flatMap { diff --git a/src/main/scala/millfork/parser/MfParser.scala b/src/main/scala/millfork/parser/MfParser.scala index e25b1466..3a07e78d 100644 --- a/src/main/scala/millfork/parser/MfParser.scala +++ b/src/main/scala/millfork/parser/MfParser.scala @@ -378,7 +378,7 @@ abstract class MfParser[T](fileId: String, input: String, currentDirectory: Stri def asmStatement: P[ExecutableStatement] - def statement: P[Seq[Statement]] = (position() ~ P(keywordStatement | localVariableDefinition | expressionStatement)).map { case (p, s) => s.map(_.pos(p)) } + def statement: P[Seq[Statement]] = (position() ~ P(keywordStatement | arrayDefinition | localVariableDefinition | expressionStatement)).map { case (p, s) => s.map(_.pos(p)) } def asmStatements: P[List[ExecutableStatement]] = ("{" ~/ AWS ~/ asmStatement.rep(sep = NoCut(EOL) ~ !"}" ~/ Pass) ~/ AWS ~/ "}" ~/ Pass).map(_.toList) diff --git a/src/test/scala/millfork/test/ArraySuite.scala b/src/test/scala/millfork/test/ArraySuite.scala index 0f7ca860..be63e715 100644 --- a/src/test/scala/millfork/test/ArraySuite.scala +++ b/src/test/scala/millfork/test/ArraySuite.scala @@ -340,4 +340,39 @@ class ArraySuite extends FunSuite with Matchers { m.readByte(0xc027) should equal(0) } } + + test("Local arrays") { + EmuUnoptimizedRun( + """ + | byte output @$c000 + | void main () { + | array square[5] + | square[0] = 1 + | output = square[0] + | } + """.stripMargin).readByte(0xc000) should equal(1) + ShouldNotCompile( + """ + | void main () { + | array square = [0] + | } + """.stripMargin) + ShouldNotCompile( + """ + | void f() { + | square[1] = 1 + | } + | void main () { + | array square = [0] + | } + """.stripMargin) + EmuUnoptimizedRun( + """ + | byte output @$c000 + | void main () { + | const array square = [1] + | output = square[0] + | } + """.stripMargin).readByte(0xc000) should equal(1) + } }