From 8d9bc2f5ff457ba76e836ed79909f299421807a6 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Sat, 12 Oct 2024 02:56:36 +0200 Subject: [PATCH] fixing all sorts of things about assigning arrays to arrays --- README.md | 16 ++-- .../src/prog8/codegen/cpu6502/AsmGen.kt | 8 +- .../optimizer/ConstantIdentifierReplacer.kt | 24 ++++++ .../compiler/astprocessing/AstChecker.kt | 69 +++++++++++------ .../astprocessing/StatementReorderer.kt | 7 +- compiler/test/TestSubroutines.kt | 2 +- .../test/codegeneration/TestArrayThings.kt | 76 +++++++++++++++++++ compiler/test/codegeneration/TestVariables.kt | 4 +- docs/source/comparing.rst | 8 +- docs/source/index.rst | 14 ++-- docs/source/programming.rst | 6 +- docs/source/todo.rst | 1 + examples/test.p8 | 50 +++++------- 13 files changed, 196 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index 5ee6406e2..488560a45 100644 --- a/README.md +++ b/README.md @@ -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:* diff --git a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt index 5b152ad22..a099bf80c 100644 --- a/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt +++ b/codeGenCpu6502/src/prog8/codegen/cpu6502/AsmGen.kt @@ -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 } diff --git a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt index daf895fa7..b578073b6 100644 --- a/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt +++ b/codeOptimizers/src/prog8/optimizer/ConstantIdentifierReplacer.kt @@ -392,11 +392,35 @@ internal class ConstantIdentifierReplacer( return noModifications } + override fun after(assignment: Assignment, parent: Node): Iterable { + // 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 -> { diff --git a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt index 0a74a4939..dcd0eb26b 100644 --- a/compiler/src/prog8/compiler/astprocessing/AstChecker.kt +++ b/compiler/src/prog8/compiler/astprocessing/AstChecker.kt @@ -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) } diff --git a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt index cb10623ef..2fc9b67cb 100644 --- a/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt +++ b/compiler/src/prog8/compiler/astprocessing/StatementReorderer.kt @@ -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)) { diff --git a/compiler/test/TestSubroutines.kt b/compiler/test/TestSubroutines.kt index b5bcff5db..17d16dc8a 100644 --- a/compiler/test/TestSubroutines.kt +++ b/compiler/test/TestSubroutines.kt @@ -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") { diff --git a/compiler/test/codegeneration/TestArrayThings.kt b/compiler/test/codegeneration/TestArrayThings.kt index d146a7777..72099cc11 100644 --- a/compiler/test/codegeneration/TestArrayThings.kt +++ b/compiler/test/codegeneration/TestArrayThings.kt @@ -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" + } }) diff --git a/compiler/test/codegeneration/TestVariables.kt b/compiler/test/codegeneration/TestVariables.kt index 51bafd8e8..4af007a5e 100644 --- a/compiler/test/codegeneration/TestVariables.kt +++ b/compiler/test/codegeneration/TestVariables.kt @@ -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") { diff --git a/docs/source/comparing.rst b/docs/source/comparing.rst index 134e8c377..7e19eb097 100644 --- a/docs/source/comparing.rst +++ b/docs/source/comparing.rst @@ -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:: diff --git a/docs/source/index.rst b/docs/source/index.rst index bc8100ed6..e310afbd7 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -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 diff --git a/docs/source/programming.rst b/docs/source/programming.rst index ec049e9f0..e2fdc0b05 100644 --- a/docs/source/programming.rst +++ b/docs/source/programming.rst @@ -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] {....}`` diff --git a/docs/source/todo.rst b/docs/source/todo.rst index 24e496914..fd8bf870c 100644 --- a/docs/source/todo.rst +++ b/docs/source/todo.rst @@ -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?) diff --git a/examples/test.p8 b/examples/test.p8 index 10a85aec4..ca6a7ddd9 100644 --- a/examples/test.p8 +++ b/examples/test.p8 @@ -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 } }