Remove array initialization by single value.

New compiler and kotlin version.
This commit is contained in:
Irmen de Jong 2024-10-13 03:45:30 +02:00
parent fa5479ee5f
commit 7a0eaf3148
11 changed files with 139 additions and 207 deletions

View File

@ -1,23 +1,23 @@
<component name="libraryTable">
<library name="KotlinJavaRuntime" type="repository">
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.20" />
<properties maven-id="org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.0.21" />
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.20/kotlin-stdlib-jdk8-2.0.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.20/kotlin-stdlib-2.0.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk8/2.0.21/kotlin-stdlib-jdk8-2.0.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib/2.0.21/kotlin-stdlib-2.0.21-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.20/kotlin-stdlib-jdk7-2.0.20-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/kotlin/kotlin-stdlib-jdk7/2.0.21/kotlin-stdlib-jdk7-2.0.21-sources.jar!/" />
</SOURCES>
</library>
</component>

View File

@ -268,7 +268,6 @@ class VarConstantValueTypeAdjuster(
// Replace all constant identifiers with their actual value,
// and the array var initializer values and sizes.
// This is needed because further constant optimizations depend on those.
internal class ConstantIdentifierReplacer(
private val program: Program,
@ -421,104 +420,41 @@ internal class ConstantIdentifierReplacer(
return null
}
// convert the initializer range expression from a range or int, to an actual array.
val rangeExpr = decl.value as? RangeExpression ?: return null
// convert the initializer range expression from a range, to an actual array literal.
val declArraySize = decl.arraysize?.constIndex()
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0)
errors.err("descending range with positive step", decl.value?.position!!)
else if(constRange.first<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange?.isEmpty()==true) {
if(constRange.first>constRange.last && constRange.step>=0)
errors.err("descending range with positive step", decl.value?.position!!)
else if(constRange.first<constRange.last && constRange.step<=0)
errors.err("ascending range with negative step", decl.value?.position!!)
}
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
return if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
}
val numericLv = decl.value as? NumericLiteral
if(numericLv!=null && numericLv.type== DataType.FLOAT)
errors.err("arraysize requires only integers here", numericLv.position)
val size = decl.arraysize?.constIndex() ?: return null
if (rangeExpr==null && numericLv!=null) {
// arraysize initializer is empty or a single int, and we know the size; create the arraysize.
val fillvalue = numericLv.number.toInt()
when(decl.datatype){
DataType.ARRAY_UB -> {
if(fillvalue !in 0..255)
errors.err("ubyte value overflow", numericLv.position)
}
DataType.ARRAY_B -> {
if(fillvalue !in -128..127)
errors.err("byte value overflow", numericLv.position)
}
DataType.ARRAY_UW -> {
if(fillvalue !in 0..65535)
errors.err("uword value overflow", numericLv.position)
}
DataType.ARRAY_W -> {
if(fillvalue !in -32768..32767)
errors.err("word value overflow", numericLv.position)
}
else -> {}
}
// create the array itself, filled with the fillvalue.
val array = Array(size) {fillvalue}.map { NumericLiteral(ArrayToElementTypes.getValue(decl.datatype), it.toDouble(), numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(decl.datatype), array, position = numericLv.position)
}
}
DataType.ARRAY_F -> {
val rangeExpr = decl.value as? RangeExpression
if(rangeExpr!=null) {
// convert the initializer range expression to an actual array of floats
val declArraySize = decl.arraysize?.constIndex()
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
val constRange = rangeExpr.toConstantIntegerRange()
if(constRange!=null) {
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
val eltType = rangeExpr.inferType(program).getOr(DataType.UBYTE)
return if(eltType in ByteDatatypes) {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
} else {
ArrayLiteral(InferredTypes.InferredType.known(decl.datatype),
constRange.map { NumericLiteral(eltType, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(rangeExpr==null && numericLv!=null) {
// arraysize initializer is a single int, and we know the array size.
val fillvalue = numericLv.number
if (fillvalue < options.compTarget.machine.FLOAT_MAX_NEGATIVE || fillvalue > options.compTarget.machine.FLOAT_MAX_POSITIVE)
errors.err("float value overflow", numericLv.position)
else {
val array = Array(size) {fillvalue}.map { NumericLiteral(DataType.FLOAT, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F), array, position = numericLv.position)
}
}
}
DataType.ARRAY_BOOL -> {
val numericLv = decl.value as? NumericLiteral
val size = decl.arraysize?.constIndex() ?: return null
if(numericLv!=null) {
// arraysize initializer is a single value, and we know the array size.
if(numericLv.type!=DataType.BOOL) {
errors.err("initializer value is not a boolean", numericLv.position)
return null
}
val array = Array(size) {numericLv.number}.map { NumericLiteral(DataType.BOOL, it, numericLv.position) }.toTypedArray<Expression>()
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_BOOL), array, position = numericLv.position)
DataType.ARRAY_F -> {
if(declArraySize!=null && declArraySize!=rangeExpr.size())
errors.err("range expression size (${rangeExpr.size()}) doesn't match declared array size ($declArraySize)", decl.value?.position!!)
if(constRange!=null) {
return ArrayLiteral(InferredTypes.InferredType.known(DataType.ARRAY_F),
constRange.map { NumericLiteral(DataType.FLOAT, it.toDouble(), decl.value!!.position) }.toTypedArray(),
position = decl.value!!.position)
}
}
else -> return null

View File

@ -716,12 +716,8 @@ internal class AstChecker(private val program: Program,
if(decl.isArray && decl.arraysize==null) {
if(decl.type== VarDeclType.MEMORY)
err("memory mapped array must have a size specification")
if(decl.value==null) {
valueerr("array variable is missing a size specification or an initialization value")
return
}
if(decl.value is NumericLiteral) {
valueerr("unsized array declaration cannot use a single literal initialization value")
if(decl.value==null || decl.value is NumericLiteral) {
err("array variable is missing a size specification")
return
}
if(decl.value is RangeExpression)
@ -802,6 +798,30 @@ internal class AstChecker(private val program: Program,
// array length limits and constant lenghts
if(decl.isArray) {
if(decl.type!=VarDeclType.MEMORY) {
// memory-mapped arrays are initialized with their address, but any other array needs a range or array literal value.
if (decl.value!=null && decl.value !is ArrayLiteral && decl.value !is RangeExpression) {
var suggestion: String? = null
val arraysize = decl.arraysize?.constIndex()
val numericvalue = decl.value?.constValue(program)
if (numericvalue != null && arraysize != null) {
when (numericvalue.type) {
in IntegerDatatypes -> suggestion = "[${numericvalue.number.toInt()}] * $arraysize"
DataType.FLOAT -> suggestion = "[${numericvalue.number}] * $arraysize"
DataType.BOOL -> suggestion = "[${numericvalue.asBooleanValue}] * $arraysize"
else -> {}
}
}
if (suggestion != null)
valueerr("array initialization value must be a range value or an array literal (suggestion: use '$suggestion' here)")
else
valueerr("array initialization value must be a range value or an array literal")
}
}
val length = decl.arraysize?.constIndex()
if(length==null)
err("array length must be known at compile-time")
@ -1831,14 +1851,7 @@ internal class AstChecker(private val program: Program,
DataType.UWORD -> sourceDatatype == DataType.UBYTE || sourceDatatype == DataType.UWORD
DataType.FLOAT -> sourceDatatype in NumericDatatypes
DataType.STR -> sourceDatatype == DataType.STR
else -> {
if(targetDatatype in ArrayDatatypes && sourceValue is ArrayLiteral)
true // assigning array literal to an array variable is allowed, size and type are checked elsewhere
else {
errors.err("cannot assign this value to variable of type $targetDatatype", position)
false
}
}
else -> targetDatatype in ArrayDatatypes && sourceValue is ArrayLiteral
}
if(result)

View File

@ -104,7 +104,7 @@ class TestAstChecks: FunSpec({
val text = """
main {
sub start() {
const ubyte[5] a = 5
const ubyte[5] a = [1,2,3,4,5]
a[2]=42
}
}
@ -133,22 +133,6 @@ class TestAstChecks: FunSpec({
errors.errors[0] shouldContain "indexing requires"
}
test("array decl with expression as size can be initialized with a single value") {
val text = """
main {
sub start() {
const ubyte n = 40
const ubyte half = n / 2
ubyte[half] @shared a = 5
}
}
"""
val errors = ErrorReporterForTests(keepMessagesAfterReporting = true)
compileText(C64Target(), true, text, writeAssembly = true, errors=errors) shouldNotBe null
errors.errors.size shouldBe 0
errors.warnings.size shouldBe 0
}
test("unicode in identifier names is working") {
val text = """
%import floats

View File

@ -113,32 +113,4 @@ class TestVariables: FunSpec({
errors.errors[0] shouldContain "value has incompatible type"
errors.errors[1] shouldContain "value has incompatible type"
}
test("initialization of boolean array with single value") {
val text = """
main {
sub start() {
bool[10] sieve0 = false
bool[10] sieve1 = true
sieve0[0] = true
sieve1[0] = true
}
}
"""
compileText(C64Target(), false, text, writeAssembly = true) shouldNotBe null
}
test("initialization of boolean array with single value of wrong type fails") {
val text = """
main {
sub start() {
bool[10] sieve2 = 42
}
}
"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 1
errors.errors[0] shouldContain "initializer value is not a boolean"
}
})

View File

@ -290,7 +290,7 @@ always have to be constants. Here are some examples of arrays::
byte[10] array ; array of 10 bytes, initially set to 0
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
ubyte[99] array = 255 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
ubyte[99] array = [255]*99 ; initialize array with 99 times 255 [255, 255, 255, 255, ...]
byte[] array = 100 to 199 ; initialize array with [100, 101, ..., 198, 199]
str[] names = ["ally", "pete"] ; array of string pointers/addresses (equivalent to array of uwords)
uword[] others = [names, array] ; array of pointers/addresses to other arrays
@ -306,7 +306,8 @@ always have to be constants. Here are some examples of arrays::
This means byte arrays should be <= 256 elements, word arrays <= 128 elements (256 if
it's a split array - see below), and float arrays <= 51 elements.
You can write out an array initializer list over several lines if you want to improve readability.
Arrays can be initialized with a range expression or an array literal value.
You can write out such an initializer value over several lines if you want to improve readability.
Note that the various keywords for the data type and variable type (``byte``, ``word``, ``const``, etc.)
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``

View File

@ -363,7 +363,7 @@ Various examples::
bool flag = true
byte[] values = [11, 22, 33, 44, 55]
byte[5] values ; array of 5 bytes, initially set to zero
byte[5] values = 255 ; initialize with five 255 bytes
byte[5] values = [255]*5 ; initialize with five 255 bytes
word @zp zpword = 9999 ; prioritize this when selecting vars for zeropage storage
uword @requirezp zpaddr = $3000 ; we require this variable in zeropage
@ -403,8 +403,11 @@ type identifier type storage size example var declara
implicitly terminated by a 0-byte
=============== ======================= ================= =========================================
**arrays:** you can split an array initializer list over several lines if you want. When an initialization
value is given, the array size in the declaration can be omitted.
**arrays:**
Arrays can be initialized with a range expression or an array literal value.
You can write out such an initializer value over several lines if you want to improve readability.
When an initialization value is given, you are allowed to omit the array size in the declaration,
because it can be inferred from the initialization value.
**numbers:** unless prefixed for hex or binary as described below, all numbers are decimal numbers. There is no octal notation.
@ -542,9 +545,10 @@ memory at the given index (and allows index values of word size). See :ref:`poin
String
^^^^^^
A string literal can occur with or without an encoding prefix (encoding followed by ':' followed by the string itself).
String length is limited to 255 characters.
You can use '+' and '*' to concatenate or repeat string fragments to build up a larger string literal.
When this is omitted, the string is stored in the machine's default character encoding (which is PETSCII on the CBM machines).
You can choose to store the string in other encodings such as ``sc`` (screencodes) or ``iso`` (iso-8859-15).
String length is limited to 255 characters.
Here are examples of the various encodings:
- ``"hello"`` a string translated into the default character encoding (PETSCII on the CBM machines)

View File

@ -1,6 +1,12 @@
TODO
====
- remove support for array variable initialization with a single value, just require explicitly creating the value array [42] * 10 (which is what the compiler now does for you implicitly)
- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. Or just use memcopy.
- should we add a cleararray builtin function that can efficiently set every element in the array to the given value
Improve register load order in subroutine call args assignments:
in certain situations, the "wrong" order of evaluation of function call arguments is done which results
in overwriting registers that already got their value, which requires a lot of stack juggling (especially on plain 6502 cpu!)
@ -9,11 +15,6 @@ Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEva
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- remove support for array variable initialization with a single value, just require explicitly creating the value array [42] * 10 (which is what the compiler now does for you implicitly)
- should the array-to-array assignment support be removed and instead require an explicit copy function call? What prog8_lib_arraycopy() now does. Or just use memcopy.
- should we add a cleararray builtin function that can efficiently set every element in the array to the given value
- improve detection that a variable is not read before being written so that initializing it to zero can be omitted (only happens now if a vardecl is immediately followed by a for loop for instance)
- Improve the SublimeText syntax file for prog8, you can also install this for 'bat': https://github.com/sharkdp/bat?tab=readme-ov-file#adding-new-syntaxes--language-definitions
- Can we support signed % (remainder) somehow?

View File

@ -12,9 +12,9 @@ main {
float[] zcoor = [ -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0 ]
; storage for rotated coordinates
float[len(xcoor)] rotatedx=0.0
float[len(ycoor)] rotatedy=0.0
float[len(zcoor)] rotatedz=-1.0
float[len(xcoor)] rotatedx
float[len(ycoor)] rotatedy
float[len(zcoor)] rotatedz
sub start() {
float time=0.0

View File

@ -1,42 +1,63 @@
%import monogfx
%import floats
%import textio
%import math
%option no_sysinit
%zeropage basicsafe
main {
str name = "xyz" * 3
bool[3] boolarray = true
ubyte[3] bytearray = 52
uword[3] wordarray = 5544
float[3] floatarray = 123.45
ubyte[3] bytearray2 = 10 to 12
uword[3] wordarray2 = 5540 to 5542
float[3] floatarray2 = 123 to 125
bool[3] boolarray3
ubyte[3] bytearray3
uword[3] wordarray3
float[3] floatarray3
sub start() {
monogfx.lores()
demofill()
}
sub demofill() {
const uword offsetx = 0
const uword offsety = 0
monogfx.circle(offsetx+160, offsety+120, 110, true)
monogfx.rect(offsetx+180, offsety+5, 25, 190, true)
monogfx.line(offsetx+100, offsety+150, offsetx+240, offsety+10, true)
monogfx.line(offsetx+101, offsety+150, offsetx+241, offsety+10, true)
monogfx.rect(offsetx+150, offsety+130, 10, 100, true)
sys.wait(30)
cbm.SETTIM(0,0,0)
monogfx.fill(offsetx+100,offsety+100,true)
monogfx.fill(offsetx+100,offsety+100,false)
uword duration = cbm.RDTIM16()
sys.wait(30)
monogfx.textmode()
txt.print(name)
txt.nl()
for cx16.r1L in 0 to 2 {
txt.print_bool(boolarray[cx16.r1L])
txt.spc()
txt.print_ub(bytearray[cx16.r1L])
txt.spc()
txt.print_uw(wordarray[cx16.r1L])
txt.spc()
floats.print(floatarray[cx16.r1L])
txt.nl()
}
txt.nl()
txt.nl()
txt.print_uw(duration)
txt.print(" jiffies\n")
; before optimizations: ~166 jiffies
for cx16.r1L in 0 to 2 {
txt.print_ub(bytearray2[cx16.r1L])
txt.spc()
txt.print_uw(wordarray2[cx16.r1L])
txt.spc()
floats.print(floatarray2[cx16.r1L])
txt.nl()
}
txt.nl()
txt.nl()
for cx16.r1L in 0 to 2 {
txt.print_bool(boolarray3[cx16.r1L])
txt.spc()
txt.print_ub(bytearray3[cx16.r1L])
txt.spc()
txt.print_uw(wordarray3[cx16.r1L])
txt.spc()
floats.print(floatarray3[cx16.r1L])
txt.nl()
}
txt.nl()
}
}

View File

@ -4,5 +4,5 @@ org.gradle.parallel=true
org.gradle.daemon=true
kotlin.code.style=official
javaVersion=11
kotlinVersion=2.0.20
version=10.4.2
kotlinVersion=2.0.21
version=10.5-SNAPSHOT