fixing all sorts of things about assigning arrays to arrays

This commit is contained in:
Irmen de Jong 2024-10-12 02:56:36 +02:00
parent 7651ccc84e
commit 8d9bc2f5ff
13 changed files with 196 additions and 89 deletions

View File

@ -54,27 +54,23 @@ What does Prog8 provide?
------------------------
- all advantages of a higher level language over having to write assembly code manually
- programs run very fast because compilation to native machine code
- programs run very fast because it's compiled to native machine code
- code often is smaller and faster than equivalent C code compiled with CC65 or even LLVM-MOS
- modularity, symbol scoping, subroutines
- modularity, symbol scoping, subroutines. No need for forward declarations.
- various data types other than just bytes (16-bit words, floats, strings)
- floating point math is supported if the target system provides floating point library routines (C64 and Cx16 both do)
- floating point math is supported on certain targets
- strings can contain escaped characters but also many symbols directly if they have a petscii equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \, {, } and | are also accepted and converted to the closest petscii equivalents.
- automatic static variable allocations, automatic string and array variables and string sharing
- subroutines with input parameters and result values
- high-level program optimizations
- no need for forward declarations
- small program boilerplate/compilersupport overhead
- programs can be run multiple times without reloading because of automatic variable (re)initializations.
- conditional branches
- conditional branches that map 1:1 to cpu status flags
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
- ``in`` expression for concise and efficient multi-value/containment check
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- various powerful built-in libraries to do I/O, number conversions, graphics and more
- convenience abstractions for low level aspects such as ZeroPage handling, program startup, explicit memory addresses
- inline assembly allows you to have full control when every cycle or byte matters
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16, and provides them also on the C64.
- encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16)
- supports the sixteen 'virtual' 16-bit registers R0 - R15 from the Commander X16 (also available on other targets)
- encode strings and characters into petscii or screencodes or even other encodings
*Rapid edit-compile-run-debug cycle:*

View File

@ -104,7 +104,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
is PtIdentifier -> parent.children[index] = node.prefix(parent, st)
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
is PtJump -> parent.children[index] = node.prefix(parent, st)
is PtVariable -> parent.children[index] = node.prefix(st)
is PtVariable -> parent.children[index] = node.prefix(parent, st)
else -> throw AssemblyError("weird node to prefix $node")
}
}
@ -135,7 +135,7 @@ private fun prefixScopedName(name: String, type: Char): String {
return prefixed.joinToString(".")
}
private fun PtVariable.prefix(st: SymbolTable): PtVariable {
private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
name = prefixScopedName(name, 'v')
if(value==null)
return this
@ -163,7 +163,9 @@ private fun PtVariable.prefix(st: SymbolTable): PtVariable {
else -> throw AssemblyError("weird array value element $elt")
}
}
PtVariable(name, type, zeropage, newValue, arraySize, position)
val result = PtVariable(name, type, zeropage, newValue, arraySize, position)
result.parent = parent
result
}
else this
}

View File

@ -392,11 +392,35 @@ internal class ConstantIdentifierReplacer(
return noModifications
}
override fun after(assignment: Assignment, parent: Node): Iterable<IAstModification> {
// convert a range expression that is assigned to an array, to an array literal instead.
val range = assignment.value as? RangeExpression
if(range!=null) {
val targetDatatype = assignment.target.inferType(program)
if(targetDatatype.isArray) {
val decl = VarDecl(VarDeclType.VAR, VarDeclOrigin.ARRAYLITERAL, targetDatatype.getOr(DataType.UNDEFINED),
ZeropageWish.DONTCARE, null, "dummy", emptyList(),
assignment.value, false, false, Position.DUMMY)
val replaceValue = createConstArrayInitializerValue(decl)
if(replaceValue!=null) {
return listOf(IAstModification.ReplaceNode(assignment.value, replaceValue, assignment))
}
}
}
return noModifications
}
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
if(decl.type==VarDeclType.MEMORY)
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
val rangeSize=(decl.value as? RangeExpression)?.size()
if(rangeSize!=null && rangeSize>65535) {
errors.err("range size overflow", decl.value!!.position)
return null
}
// convert the initializer range expression from a range or int, to an actual array.
when(decl.datatype) {
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {

View File

@ -16,7 +16,7 @@ import kotlin.io.path.Path
import kotlin.math.floor
/**
* Semantic analysis.
* Semantic analysis and error reporting.
*/
internal class AstChecker(private val program: Program,
private val errors: IErrorReporter,
@ -665,7 +665,7 @@ internal class AstChecker(private val program: Program,
if (assignment.value !is BinaryExpression && assignment.value !is PrefixExpression && assignment.value !is ContainmentCheck)
errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position)
} else {
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
checkAssignmentCompatible(assignTarget, targetDatatype.getOr(DataType.UNDEFINED),
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
}
}
@ -696,6 +696,7 @@ internal class AstChecker(private val program: Program,
}
fun err(msg: String) = errors.err(msg, decl.position)
fun valueerr(msg: String) = errors.err(msg, decl.value?.position ?: decl.position)
// the initializer value can't refer to the variable itself (recursive definition)
if(decl.value?.referencesIdentifier(listOf(decl.name)) == true || decl.arraysize?.indexExpr?.referencesIdentifier(listOf(decl.name)) == true)
@ -716,11 +717,11 @@ internal class AstChecker(private val program: Program,
if(decl.type== VarDeclType.MEMORY)
err("memory mapped array must have a size specification")
if(decl.value==null) {
err("array variable is missing a size specification or an initialization value")
valueerr("array variable is missing a size specification or an initialization value")
return
}
if(decl.value is NumericLiteral) {
err("unsized array declaration cannot use a single literal initialization value")
valueerr("unsized array declaration cannot use a single literal initialization value")
return
}
if(decl.value is RangeExpression)
@ -746,7 +747,7 @@ internal class AstChecker(private val program: Program,
}
else -> {
if(decl.type==VarDeclType.CONST) {
err("const declaration needs a compile-time constant initializer value")
valueerr("const declaration needs a compile-time constant initializer value")
super.visit(decl)
return
}
@ -776,10 +777,10 @@ internal class AstChecker(private val program: Program,
val numvalue = decl.value as? NumericLiteral
if(numvalue!=null) {
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
err("memory address must be valid integer 0..\$ffff")
valueerr("memory address must be valid integer 0..\$ffff")
}
} else {
err("value of memory mapped variable can only be a constant, maybe use an address pointer type instead?")
valueerr("value of memory mapped variable can only be a constant, maybe use an address pointer type instead?")
}
}
}
@ -791,10 +792,10 @@ internal class AstChecker(private val program: Program,
if(decl.isArray) {
val eltDt = ArrayToElementTypes.getValue(decl.datatype)
if(iDt isnot eltDt)
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
valueerr("value has incompatible type ($iDt) for the variable (${decl.datatype})")
} else {
if(!(iDt.isBool && decl.datatype==DataType.UBYTE || iDt.istype(DataType.UBYTE) && decl.datatype==DataType.BOOL))
err("initialisation value has incompatible type ($iDt) for the variable (${decl.datatype})")
valueerr("value has incompatible type ($iDt) for the variable (${decl.datatype})")
}
}
}
@ -843,7 +844,7 @@ internal class AstChecker(private val program: Program,
if(decl.type==VarDeclType.MEMORY)
err("strings can't be memory mapped")
else
err("string var must be initialized with a string literal")
valueerr("string var must be initialized with a string literal")
}
}
@ -1009,9 +1010,11 @@ internal class AstChecker(private val program: Program,
}
if(array.parent is Assignment) {
val arraydt = array.inferType(program)
val assignTarget = (array.parent as Assignment).target
if(!assignTarget.inferType(program).isArray)
errors.err("cannot assign array to a non-array variable", assignTarget.position)
val targetDt = assignTarget.inferType(program)
if(arraydt!=targetDt)
errors.err("value has incompatible type ($arraydt) for the variable ($targetDt)", array.position)
}
super.visit(array)
}
@ -1625,7 +1628,7 @@ internal class AstChecker(private val program: Program,
return err("boolean array length must be 1-256")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
}
return err("invalid boolean array size, must be 1-256")
@ -1644,7 +1647,7 @@ internal class AstChecker(private val program: Program,
return err("byte array length must be 1-256")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
}
return err("invalid byte array size, must be 1-256")
@ -1664,7 +1667,7 @@ internal class AstChecker(private val program: Program,
return err("array length must be 1-$maxLength")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
return true
}
return err("invalid array size, must be 1-$maxLength")
@ -1683,7 +1686,7 @@ internal class AstChecker(private val program: Program,
return err("float array length must be 1-51")
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
if (arraySize != expectedSize)
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
} else
return err("invalid float array size, must be 1-51")
@ -1752,7 +1755,7 @@ internal class AstChecker(private val program: Program,
return true
}
private fun checkArrayValues(value: ArrayLiteral, type: DataType): Boolean {
private fun checkArrayValues(value: ArrayLiteral, targetDt: DataType): Boolean {
val array = value.value.map {
when (it) {
is NumericLiteral -> it.number.toInt()
@ -1764,13 +1767,13 @@ internal class AstChecker(private val program: Program,
if(cast==null || !cast.isValid)
-9999999
else
cast.valueOrZero().number.toInt()
cast.valueOrZero().number
}
else -> -9999999
}
}
val correct: Boolean
when (type) {
when (targetDt) {
DataType.ARRAY_UB -> {
correct = array.all { it in 0..255 }
}
@ -1787,18 +1790,29 @@ internal class AstChecker(private val program: Program,
correct = array.all { it==0 || it==1 }
}
DataType.ARRAY_F -> correct = true
else -> throw FatalAstException("invalid array type $type")
else -> throw FatalAstException("invalid type $targetDt")
}
if (!correct)
errors.err("array value out of range for type $type", value.position)
errors.err("array element out of range for type $targetDt", value.position)
return correct
}
private fun checkAssignmentCompatible(targetDatatype: DataType,
private fun checkAssignmentCompatible(target: AssignTarget,
targetDatatype: DataType,
sourceDatatype: DataType,
sourceValue: Expression) : Boolean {
val position = sourceValue.position
if(sourceValue is ArrayLiteral && targetDatatype in ArrayDatatypes) {
val vardecl=target.identifier?.targetVarDecl(program)
val targetSize = vardecl?.arraysize?.constIndex()
if(targetSize!=null) {
if(sourceValue.value.size != targetSize) {
errors.err("array size mismatch (expecting $targetSize, got ${sourceValue.value.size})", sourceValue.position)
}
}
}
if(sourceValue is RangeExpression) {
errors.err("can't assign a range value to something else", position)
return false
@ -1818,8 +1832,12 @@ internal class AstChecker(private val program: Program,
DataType.FLOAT -> sourceDatatype in NumericDatatypes
DataType.STR -> sourceDatatype == DataType.STR
else -> {
errors.err("cannot assign new value to variable of type $targetDatatype", position)
false
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 new value to variable of type $targetDatatype", position)
false
}
}
}
@ -1840,6 +1858,9 @@ internal class AstChecker(private val program: Program,
else if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes) {
// this is allowed: a pass-by-reference datatype into a uword (pointer value).
}
else if(sourceDatatype in ArrayDatatypes && targetDatatype in ArrayDatatypes) {
// this is allowed (assigning array to array)
}
else if(sourceDatatype==DataType.BOOL && targetDatatype!=DataType.BOOL) {
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}

View File

@ -255,18 +255,17 @@ internal class StatementReorderer(
if(assign.value is ArrayLiteral) {
return // invalid assignment of literals will be reported elsewhere
}
if(assign.value !is IdentifierReference) {
errors.err("invalid array value to assign to other array", assign.value.position)
return
return // invalid assignment value will be reported elsewhere
}
val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program)!!
if(!sourceVar.isArray) {
errors.err("value must be an array", sourceIdent.position)
} else {
if (sourceVar.arraysize!!.constIndex() != targetVar.arraysize!!.constIndex())
errors.err("element count mismatch", assign.position)
errors.err("array size mismatch (expecting ${targetVar.arraysize!!.constIndex()}, got ${sourceVar.arraysize!!.constIndex()})", assign.value.position)
val sourceEltDt = ArrayToElementTypes.getValue(sourceVar.datatype)
val targetEltDt = ArrayToElementTypes.getValue(targetVar.datatype)
if (!sourceEltDt.equalsSize(targetEltDt)) {

View File

@ -33,7 +33,7 @@ class TestSubroutines: FunSpec({
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "type mismatch, was: STR expected: UBYTE"
errors.errors[1] shouldContain "initialisation value has incompatible type"
errors.errors[1] shouldContain "value has incompatible type"
}
test("stringParameter") {

View File

@ -4,6 +4,7 @@ import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.matchers.string.shouldContain
import prog8.code.ast.PtBuiltinFunctionCall
import prog8.code.target.C64Target
import prog8.code.target.VMTarget
import prog8tests.helpers.ErrorReporterForTests
@ -327,5 +328,80 @@ main {
errors.errors[1] shouldContain "out of bounds"
errors.errors[2] shouldContain "out of bounds"
}
test("array assignments should check for number of elements and element type correctness") {
val src="""
%option enable_floats
main {
sub start() {
ubyte[] array = 1 to 4
ubyte[] array2 = [1,2,3,4]
str[] names = ["apple", "banana", "tomato"]
array = [10,11,12,13] ; ok!
array = 20 to 23 ; ok!
names = ["x1", "x2", "x3"] ; ok!
ubyte[] array3 = [1,2,3,4000] ; error: element type
array = 10 to 15 ; error: array size
array = 1000 to 1003 ; error: element type
names = ["x1", "x2", "x3", "x4"] ; error: array size
names = [1.1, 2.2, 3.3, 4.4] ; error: array size AND element type
names = [1.1, 2.2, 999999.9] ; error: element type
names = [1.1, 2.2, 9.9] ; error: element type
}
}"""
val errors = ErrorReporterForTests()
compileText(C64Target(), false, src, writeAssembly = true, errors = errors) shouldBe null
errors.errors.size shouldBe 8
errors.errors[0] shouldContain "incompatible type"
errors.errors[1] shouldContain "array size mismatch"
errors.errors[2] shouldContain "array element out of range"
errors.errors[3] shouldContain "array size mismatch"
errors.errors[4] shouldContain "array size mismatch"
errors.errors[5] shouldContain "value has incompatible type"
errors.errors[6] shouldContain "value has incompatible type"
errors.errors[7] shouldContain "value has incompatible type"
}
test("array assignments should work via array copy call") {
val src="""
%option enable_floats
main {
sub start() {
ubyte[] array = [1,2,3]
ubyte[3] array2
float[] flarray = [1.1, 2.2, 3.3]
float[3] flarray2
word[] warray = [-2222,42,3333]
word[3] warray2
str[] names = ["apple", "banana", "tomato"]
str[3] names2
; 8 array assignments -> 8 arraycopies:
array = [8,7,6]
array = array2
flarray = [99.9, 88.8, 77.7]
flarray = flarray2
warray = [4444,5555,6666]
warray = warray2
names = ["x1", "x2", "x3"]
names = names2
}
}"""
compileText(VMTarget(), false, src, writeAssembly = true) shouldNotBe null
val result = compileText(C64Target(), false, src, writeAssembly = true)!!
val x = result.codegenAst!!.entrypoint()!!
(x.children[12] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[13] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[14] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[15] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[16] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[17] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[18] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
(x.children[19] as PtBuiltinFunctionCall).name shouldBe "prog8_lib_arraycopy"
}
})

View File

@ -110,8 +110,8 @@ class TestVariables: FunSpec({
val errors = ErrorReporterForTests()
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
errors.errors.size shouldBe 2
errors.errors[0] shouldContain "initialisation value has incompatible type"
errors.errors[1] shouldContain "initialisation value has incompatible type"
errors.errors[0] shouldContain "value has incompatible type"
errors.errors[1] shouldContain "value has incompatible type"
}
test("initialization of boolean array with single value") {

View File

@ -34,6 +34,8 @@ Data types
You'll have to add explicit casts to increase the size of the value if required.
For example when adding two byte variables having values 100 and 200, the result won't be 300, because that doesn't fit in a byte. It will be 44.
You'll have to cast one or both of the *operands* to a word type first if you want to accomodate the actual result value of 300.
- Arrays and strings have a limited size and the allocated size never changes
- Arrays and strings are mutable
Variables
@ -85,10 +87,10 @@ Foreign function interface (external/ROM calls)
Optimizations
-------------
- Prog8 contains many compiler optimizations to generate efficent code, but also lacks many optimizations that modern compilers do have.
- Prog8 contains many compiler optimizations to generate efficient code, but also lacks many optimizations that modern compilers do have.
While empirical evidence shows that Prog8 generates more efficent code than some C compilers that also target the same 8 bit systems,
it still is limited in how sophisticated the optimizations are that it performs on your code.
- For time critical code, it may be worth it to inspect the generated assembly code to see if you could write things differently
the optimizations it makes on your code aren't super sophisticated.
- For time critical code, it may be worth it to inspect the generated assembly code to see if you can write things differently
to help the compiler generate more efficient code (or even replace it with hand written inline assembly altogether).
For example, if you repeat an expression multiple times it will be evaluated every time, so maybe you should store it
in a variable instead and reuse that variable::

View File

@ -88,30 +88,28 @@ Features
- provides a convenient and fast edit/compile/run cycle by being able to directly launch
the compiled program in an emulator and provide debugging information to this emulator.
- the language looks like a mix of Python and C so should be quite easy to learn
- Modular programming, scoping via modules, code blocks, and subroutines. No need for forward declarations.
- Provide high level programming constructs but at the same time stay close to the metal;
- Modular programming, scoping via module source files, code blocks, and subroutines. No need for forward declarations.
- Provides high level programming constructs but at the same time stay close to the metal;
still able to directly use memory addresses and ROM subroutines,
and inline assembly to have full control when every register, cycle or byte matters
- Subroutines with parameters and return values of various types
- Complex nested expressions are possible
- Variables are all allocated statically, no memory allocator overhead
- Variables are all allocated statically, no memory allocation overhead
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
- ``when`` statement to avoid if-else chains
- ``in`` expression for concise and efficient multi-value/containment test
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
- Variable data types include signed and unsigned bytes and words, arrays, strings.
- Various powerful built-in libraries to do I/O, number conversions, graphics and more
- Floating point math is supported on select compiler targets.
- Floating point math is supported on certain compiler targets.
- Easy and highly efficient integration with external subroutines and ROM routines on the target systems.
- Strings can contain escaped characters but also many symbols directly if they have a PETSCII equivalent, such as "♠♥♣♦π▚●○╳". Characters like ^, _, \\, {, } and | are also accepted and converted to the closest PETSCII equivalents.
- Encode strings and characters into petscii or screencodes or even other encodings, as desired (C64/Cx16)
- Identifiers can contain Unicode Letters, so ``knäckebröd``, ``приблизительно``, ``見せしめ`` and ``π`` are all valid identifiers.
- Advanced code optimizations to make the resulting program smaller and faster
- Programs can be restarted after exiting (i.e. run them multiple times without having to reload everything), due to automatic variable (re)initializations.
- Supports the sixteen 'virtual' 16-bit registers R0 to R15 as defined on the Commander X16. These are also available on the other compilation targets!
- Supports the sixteen 'virtual' 16-bit registers R0 to R15 as defined on the Commander X16. You can look at them as general purpose global variables. These are also available on the other compilation targets!
- On the Commander X16: Support for low level system features such as Vera Fx, which includes 16x16 bits multiplication in hardware and fast memory copy and fill.
- Many library routines are available across compiler targets. This means that as long as you only use standard Kernal
and core prog8 library routines, it is sometimes possible to compile the *exact same program* for different machines (just change the compilation target flag).
and core prog8 library routines, it is sometimes possible to compile the *exact same program* for different machines by just changing the compilation target flag.
Code example

View File

@ -312,9 +312,9 @@ Note that the various keywords for the data type and variable type (``byte``, ``
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
for instance.
It's possible to assign an array to another array; this will overwrite all elements in the target
array with those in the source array. The number and types of elements have to match for this to work!
For large arrays this is a slow operation because every element is copied over. It should probably be avoided.
It is possible to assign an array (variable or array literal) to another array; this will overwrite all elements in the target
array with those in the source array. The number of elements in the arrays and the data types have to match.
For large arrays this is a slow operation because all values are copied over.
Using the ``in`` operator you can easily check if a value is present in an array,
example: ``if choice in [1,2,3,4] {....}``

View File

@ -10,6 +10,7 @@ Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEva
Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^
- 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?
- Don't add "random" rts to %asm blocks but instead give a warning about it? (but this breaks existing behavior that others already depend on... command line switch? block directive?)

View File

@ -1,37 +1,25 @@
%import palette
%import textio
%option no_sysinit
%option no_sysinit, enable_floats
%zeropage basicsafe
main {
sub start() {
repeat 4 {
for cx16.r0L in 0 to 15 {
txt.color2(cx16.r0L, cx16.r0L)
txt.spc()
txt.spc()
txt.spc()
txt.spc()
}
txt.nl()
}
bool changed
uword[] colors = [
$f00, $800, $200, $000,
$f0f, $80f, $20f, $00f
]
do {
sys.waitvsync()
sys.waitvsync()
changed = palette.fade_step_colors(0, 8, colors)
} until not changed
ubyte[] array = [1,2,3]
ubyte[3] array2
float[] flarray = [1.1, 2.2, 3.3]
float[3] flarray2
word[] warray = [-2222,42,3333]
word[3] warray2
str[] names = ["apple", "banana", "tomato"]
str[3] names2
sys.wait(60)
changed = false
do {
sys.waitvsync()
sys.waitvsync()
changed = palette.fade_step_multi(0, 8, $fff)
} until not changed
sys.wait(60)
; 8 array assignments -> 8 arraycopies:
array = [8,7,6]
array = array2
flarray = [99.9, 88.8, 77.7]
flarray = flarray2
warray = [4444,5555,6666]
warray = warray2
names = ["x1", "x2", "x3"]
names = names2
}
}