mirror of
https://github.com/irmen/prog8.git
synced 2025-01-13 10:29:52 +00:00
fixing all sorts of things about assigning arrays to arrays
This commit is contained in:
parent
7651ccc84e
commit
8d9bc2f5ff
16
README.md
16
README.md
@ -54,27 +54,23 @@ What does Prog8 provide?
|
|||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
- all advantages of a higher level language over having to write assembly code manually
|
- 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
|
- 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)
|
- 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.
|
- 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
|
- automatic static variable allocations, automatic string and array variables and string sharing
|
||||||
- subroutines with input parameters and result values
|
|
||||||
- high-level program optimizations
|
- 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.
|
- 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
|
- ``when`` statement to provide a concise jump table alternative to if/elseif chains
|
||||||
- ``in`` expression for concise and efficient multi-value/containment check
|
- ``in`` expression for concise and efficient multi-value/containment check
|
||||||
- several specialized built-in functions such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
|
- 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
|
- 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
|
- 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.
|
- 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, as desired (C64/Cx16)
|
- encode strings and characters into petscii or screencodes or even other encodings
|
||||||
|
|
||||||
*Rapid edit-compile-run-debug cycle:*
|
*Rapid edit-compile-run-debug cycle:*
|
||||||
|
|
||||||
|
@ -104,7 +104,7 @@ class AsmGen6502(val prefixSymbols: Boolean): ICodeGeneratorBackend {
|
|||||||
is PtIdentifier -> parent.children[index] = node.prefix(parent, st)
|
is PtIdentifier -> parent.children[index] = node.prefix(parent, st)
|
||||||
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
|
is PtFunctionCall -> throw AssemblyError("PtFunctionCall should be processed in their own list, last")
|
||||||
is PtJump -> parent.children[index] = node.prefix(parent, st)
|
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")
|
else -> throw AssemblyError("weird node to prefix $node")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,7 +135,7 @@ private fun prefixScopedName(name: String, type: Char): String {
|
|||||||
return prefixed.joinToString(".")
|
return prefixed.joinToString(".")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun PtVariable.prefix(st: SymbolTable): PtVariable {
|
private fun PtVariable.prefix(parent: PtNode, st: SymbolTable): PtVariable {
|
||||||
name = prefixScopedName(name, 'v')
|
name = prefixScopedName(name, 'v')
|
||||||
if(value==null)
|
if(value==null)
|
||||||
return this
|
return this
|
||||||
@ -163,7 +163,9 @@ private fun PtVariable.prefix(st: SymbolTable): PtVariable {
|
|||||||
else -> throw AssemblyError("weird array value element $elt")
|
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
|
else this
|
||||||
}
|
}
|
||||||
|
@ -392,11 +392,35 @@ internal class ConstantIdentifierReplacer(
|
|||||||
return noModifications
|
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? {
|
private fun createConstArrayInitializerValue(decl: VarDecl): ArrayLiteral? {
|
||||||
|
|
||||||
if(decl.type==VarDeclType.MEMORY)
|
if(decl.type==VarDeclType.MEMORY)
|
||||||
return null // memory mapped arrays can never have an initializer value other than the address where they're mapped.
|
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.
|
// convert the initializer range expression from a range or int, to an actual array.
|
||||||
when(decl.datatype) {
|
when(decl.datatype) {
|
||||||
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
|
DataType.ARRAY_UB, DataType.ARRAY_B, DataType.ARRAY_UW, DataType.ARRAY_W, DataType.ARRAY_W_SPLIT, DataType.ARRAY_UW_SPLIT -> {
|
||||||
|
@ -16,7 +16,7 @@ import kotlin.io.path.Path
|
|||||||
import kotlin.math.floor
|
import kotlin.math.floor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Semantic analysis.
|
* Semantic analysis and error reporting.
|
||||||
*/
|
*/
|
||||||
internal class AstChecker(private val program: Program,
|
internal class AstChecker(private val program: Program,
|
||||||
private val errors: IErrorReporter,
|
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)
|
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)
|
errors.err("invalid assignment value, maybe forgot '&' (address-of)", assignment.value.position)
|
||||||
} else {
|
} else {
|
||||||
checkAssignmentCompatible(targetDatatype.getOr(DataType.UNDEFINED),
|
checkAssignmentCompatible(assignTarget, targetDatatype.getOr(DataType.UNDEFINED),
|
||||||
sourceDatatype.getOr(DataType.UNDEFINED), assignment.value)
|
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 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)
|
// 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)
|
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)
|
if(decl.type== VarDeclType.MEMORY)
|
||||||
err("memory mapped array must have a size specification")
|
err("memory mapped array must have a size specification")
|
||||||
if(decl.value==null) {
|
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
|
return
|
||||||
}
|
}
|
||||||
if(decl.value is NumericLiteral) {
|
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
|
return
|
||||||
}
|
}
|
||||||
if(decl.value is RangeExpression)
|
if(decl.value is RangeExpression)
|
||||||
@ -746,7 +747,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
if(decl.type==VarDeclType.CONST) {
|
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)
|
super.visit(decl)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -776,10 +777,10 @@ internal class AstChecker(private val program: Program,
|
|||||||
val numvalue = decl.value as? NumericLiteral
|
val numvalue = decl.value as? NumericLiteral
|
||||||
if(numvalue!=null) {
|
if(numvalue!=null) {
|
||||||
if (numvalue.type !in IntegerDatatypes || numvalue.number.toInt() < 0 || numvalue.number.toInt() > 65535) {
|
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 {
|
} 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) {
|
if(decl.isArray) {
|
||||||
val eltDt = ArrayToElementTypes.getValue(decl.datatype)
|
val eltDt = ArrayToElementTypes.getValue(decl.datatype)
|
||||||
if(iDt isnot eltDt)
|
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 {
|
} else {
|
||||||
if(!(iDt.isBool && decl.datatype==DataType.UBYTE || iDt.istype(DataType.UBYTE) && decl.datatype==DataType.BOOL))
|
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)
|
if(decl.type==VarDeclType.MEMORY)
|
||||||
err("strings can't be memory mapped")
|
err("strings can't be memory mapped")
|
||||||
else
|
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) {
|
if(array.parent is Assignment) {
|
||||||
|
val arraydt = array.inferType(program)
|
||||||
val assignTarget = (array.parent as Assignment).target
|
val assignTarget = (array.parent as Assignment).target
|
||||||
if(!assignTarget.inferType(program).isArray)
|
val targetDt = assignTarget.inferType(program)
|
||||||
errors.err("cannot assign array to a non-array variable", assignTarget.position)
|
if(arraydt!=targetDt)
|
||||||
|
errors.err("value has incompatible type ($arraydt) for the variable ($targetDt)", array.position)
|
||||||
}
|
}
|
||||||
super.visit(array)
|
super.visit(array)
|
||||||
}
|
}
|
||||||
@ -1625,7 +1628,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
return err("boolean array length must be 1-256")
|
return err("boolean array length must be 1-256")
|
||||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||||
if (arraySize != expectedSize)
|
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 true
|
||||||
}
|
}
|
||||||
return err("invalid boolean array size, must be 1-256")
|
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")
|
return err("byte array length must be 1-256")
|
||||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||||
if (arraySize != expectedSize)
|
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 true
|
||||||
}
|
}
|
||||||
return err("invalid byte array size, must be 1-256")
|
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")
|
return err("array length must be 1-$maxLength")
|
||||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||||
if (arraySize != expectedSize)
|
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 true
|
||||||
}
|
}
|
||||||
return err("invalid array size, must be 1-$maxLength")
|
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")
|
return err("float array length must be 1-51")
|
||||||
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
val expectedSize = arrayspec.constIndex() ?: return err("array size specifier must be constant integer value")
|
||||||
if (arraySize != expectedSize)
|
if (arraySize != expectedSize)
|
||||||
return err("initializer array size mismatch (expecting $expectedSize, got $arraySize)")
|
return err("array size mismatch (expecting $expectedSize, got $arraySize)")
|
||||||
} else
|
} else
|
||||||
return err("invalid float array size, must be 1-51")
|
return err("invalid float array size, must be 1-51")
|
||||||
|
|
||||||
@ -1752,7 +1755,7 @@ internal class AstChecker(private val program: Program,
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkArrayValues(value: ArrayLiteral, type: DataType): Boolean {
|
private fun checkArrayValues(value: ArrayLiteral, targetDt: DataType): Boolean {
|
||||||
val array = value.value.map {
|
val array = value.value.map {
|
||||||
when (it) {
|
when (it) {
|
||||||
is NumericLiteral -> it.number.toInt()
|
is NumericLiteral -> it.number.toInt()
|
||||||
@ -1764,13 +1767,13 @@ internal class AstChecker(private val program: Program,
|
|||||||
if(cast==null || !cast.isValid)
|
if(cast==null || !cast.isValid)
|
||||||
-9999999
|
-9999999
|
||||||
else
|
else
|
||||||
cast.valueOrZero().number.toInt()
|
cast.valueOrZero().number
|
||||||
}
|
}
|
||||||
else -> -9999999
|
else -> -9999999
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val correct: Boolean
|
val correct: Boolean
|
||||||
when (type) {
|
when (targetDt) {
|
||||||
DataType.ARRAY_UB -> {
|
DataType.ARRAY_UB -> {
|
||||||
correct = array.all { it in 0..255 }
|
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 }
|
correct = array.all { it==0 || it==1 }
|
||||||
}
|
}
|
||||||
DataType.ARRAY_F -> correct = true
|
DataType.ARRAY_F -> correct = true
|
||||||
else -> throw FatalAstException("invalid array type $type")
|
else -> throw FatalAstException("invalid type $targetDt")
|
||||||
}
|
}
|
||||||
if (!correct)
|
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
|
return correct
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkAssignmentCompatible(targetDatatype: DataType,
|
private fun checkAssignmentCompatible(target: AssignTarget,
|
||||||
|
targetDatatype: DataType,
|
||||||
sourceDatatype: DataType,
|
sourceDatatype: DataType,
|
||||||
sourceValue: Expression) : Boolean {
|
sourceValue: Expression) : Boolean {
|
||||||
val position = sourceValue.position
|
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) {
|
if(sourceValue is RangeExpression) {
|
||||||
errors.err("can't assign a range value to something else", position)
|
errors.err("can't assign a range value to something else", position)
|
||||||
return false
|
return false
|
||||||
@ -1818,8 +1832,12 @@ internal class AstChecker(private val program: Program,
|
|||||||
DataType.FLOAT -> sourceDatatype in NumericDatatypes
|
DataType.FLOAT -> sourceDatatype in NumericDatatypes
|
||||||
DataType.STR -> sourceDatatype == DataType.STR
|
DataType.STR -> sourceDatatype == DataType.STR
|
||||||
else -> {
|
else -> {
|
||||||
errors.err("cannot assign new value to variable of type $targetDatatype", position)
|
if(targetDatatype in ArrayDatatypes && sourceValue is ArrayLiteral)
|
||||||
false
|
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) {
|
else if(targetDatatype==DataType.UWORD && sourceDatatype in PassByReferenceDatatypes) {
|
||||||
// this is allowed: a pass-by-reference datatype into a uword (pointer value).
|
// 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) {
|
else if(sourceDatatype==DataType.BOOL && targetDatatype!=DataType.BOOL) {
|
||||||
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
|
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
|
||||||
}
|
}
|
||||||
|
@ -255,18 +255,17 @@ internal class StatementReorderer(
|
|||||||
if(assign.value is ArrayLiteral) {
|
if(assign.value is ArrayLiteral) {
|
||||||
return // invalid assignment of literals will be reported elsewhere
|
return // invalid assignment of literals will be reported elsewhere
|
||||||
}
|
}
|
||||||
|
|
||||||
if(assign.value !is IdentifierReference) {
|
if(assign.value !is IdentifierReference) {
|
||||||
errors.err("invalid array value to assign to other array", assign.value.position)
|
return // invalid assignment value will be reported elsewhere
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val sourceIdent = assign.value as IdentifierReference
|
val sourceIdent = assign.value as IdentifierReference
|
||||||
val sourceVar = sourceIdent.targetVarDecl(program)!!
|
val sourceVar = sourceIdent.targetVarDecl(program)!!
|
||||||
if(!sourceVar.isArray) {
|
if(!sourceVar.isArray) {
|
||||||
errors.err("value must be an array", sourceIdent.position)
|
errors.err("value must be an array", sourceIdent.position)
|
||||||
} else {
|
} else {
|
||||||
if (sourceVar.arraysize!!.constIndex() != targetVar.arraysize!!.constIndex())
|
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 sourceEltDt = ArrayToElementTypes.getValue(sourceVar.datatype)
|
||||||
val targetEltDt = ArrayToElementTypes.getValue(targetVar.datatype)
|
val targetEltDt = ArrayToElementTypes.getValue(targetVar.datatype)
|
||||||
if (!sourceEltDt.equalsSize(targetEltDt)) {
|
if (!sourceEltDt.equalsSize(targetEltDt)) {
|
||||||
|
@ -33,7 +33,7 @@ class TestSubroutines: FunSpec({
|
|||||||
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
|
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
|
||||||
errors.errors.size shouldBe 2
|
errors.errors.size shouldBe 2
|
||||||
errors.errors[0] shouldContain "type mismatch, was: STR expected: UBYTE"
|
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") {
|
test("stringParameter") {
|
||||||
|
@ -4,6 +4,7 @@ import io.kotest.core.spec.style.FunSpec
|
|||||||
import io.kotest.matchers.shouldBe
|
import io.kotest.matchers.shouldBe
|
||||||
import io.kotest.matchers.shouldNotBe
|
import io.kotest.matchers.shouldNotBe
|
||||||
import io.kotest.matchers.string.shouldContain
|
import io.kotest.matchers.string.shouldContain
|
||||||
|
import prog8.code.ast.PtBuiltinFunctionCall
|
||||||
import prog8.code.target.C64Target
|
import prog8.code.target.C64Target
|
||||||
import prog8.code.target.VMTarget
|
import prog8.code.target.VMTarget
|
||||||
import prog8tests.helpers.ErrorReporterForTests
|
import prog8tests.helpers.ErrorReporterForTests
|
||||||
@ -327,5 +328,80 @@ main {
|
|||||||
errors.errors[1] shouldContain "out of bounds"
|
errors.errors[1] shouldContain "out of bounds"
|
||||||
errors.errors[2] 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"
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -110,8 +110,8 @@ class TestVariables: FunSpec({
|
|||||||
val errors = ErrorReporterForTests()
|
val errors = ErrorReporterForTests()
|
||||||
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
|
compileText(C64Target(), false, text, writeAssembly = true, errors=errors) shouldBe null
|
||||||
errors.errors.size shouldBe 2
|
errors.errors.size shouldBe 2
|
||||||
errors.errors[0] shouldContain "initialisation value has incompatible type"
|
errors.errors[0] shouldContain "value has incompatible type"
|
||||||
errors.errors[1] shouldContain "initialisation value has incompatible type"
|
errors.errors[1] shouldContain "value has incompatible type"
|
||||||
}
|
}
|
||||||
|
|
||||||
test("initialization of boolean array with single value") {
|
test("initialization of boolean array with single value") {
|
||||||
|
@ -34,6 +34,8 @@ Data types
|
|||||||
You'll have to add explicit casts to increase the size of the value if required.
|
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.
|
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.
|
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
|
Variables
|
||||||
@ -85,10 +87,10 @@ Foreign function interface (external/ROM calls)
|
|||||||
|
|
||||||
Optimizations
|
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,
|
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.
|
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 could write things differently
|
- 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).
|
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
|
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::
|
in a variable instead and reuse that variable::
|
||||||
|
@ -88,30 +88,28 @@ Features
|
|||||||
- provides a convenient and fast edit/compile/run cycle by being able to directly launch
|
- 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 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
|
- 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.
|
- Modular programming, scoping via module source files, code blocks, and subroutines. No need for forward declarations.
|
||||||
- Provide high level programming constructs but at the same time stay close to the metal;
|
- 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,
|
still able to directly use memory addresses and ROM subroutines,
|
||||||
and inline assembly to have full control when every register, cycle or byte matters
|
and inline assembly to have full control when every register, cycle or byte matters
|
||||||
- Subroutines with parameters and return values of various types
|
- Variables are all allocated statically, no memory allocation overhead
|
||||||
- Complex nested expressions are possible
|
|
||||||
- Variables are all allocated statically, no memory allocator overhead
|
|
||||||
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
|
- Conditional branches for status flags that map 1:1 to processor branch instructions for optimal efficiency
|
||||||
- ``when`` statement to avoid if-else chains
|
- ``when`` statement to avoid if-else chains
|
||||||
- ``in`` expression for concise and efficient multi-value/containment test
|
- ``in`` expression for concise and efficient multi-value/containment test
|
||||||
- Several specialized built-in functions, such as ``lsb``, ``msb``, ``min``, ``max``, ``rol``, ``ror``
|
- 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.
|
- 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
|
- 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.
|
- 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.
|
- 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)
|
- 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.
|
- 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
|
- 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.
|
- 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.
|
- 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
|
- 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
|
Code example
|
||||||
|
@ -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``
|
can't be used as *identifiers* elsewhere. You can't make a variable, block or subroutine with the name ``byte``
|
||||||
for instance.
|
for instance.
|
||||||
|
|
||||||
It's possible to assign an array to another array; this will overwrite all elements in the target
|
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 and types of elements have to match for this to work!
|
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 every element is copied over. It should probably be avoided.
|
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,
|
Using the ``in`` operator you can easily check if a value is present in an array,
|
||||||
example: ``if choice in [1,2,3,4] {....}``
|
example: ``if choice in [1,2,3,4] {....}``
|
||||||
|
@ -10,6 +10,7 @@ Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEva
|
|||||||
Future Things and Ideas
|
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
|
- 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?
|
- 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?)
|
- 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?)
|
||||||
|
@ -1,37 +1,25 @@
|
|||||||
%import palette
|
%option no_sysinit, enable_floats
|
||||||
%import textio
|
%zeropage basicsafe
|
||||||
%option no_sysinit
|
|
||||||
|
|
||||||
main {
|
main {
|
||||||
sub start() {
|
sub start() {
|
||||||
repeat 4 {
|
ubyte[] array = [1,2,3]
|
||||||
for cx16.r0L in 0 to 15 {
|
ubyte[3] array2
|
||||||
txt.color2(cx16.r0L, cx16.r0L)
|
float[] flarray = [1.1, 2.2, 3.3]
|
||||||
txt.spc()
|
float[3] flarray2
|
||||||
txt.spc()
|
word[] warray = [-2222,42,3333]
|
||||||
txt.spc()
|
word[3] warray2
|
||||||
txt.spc()
|
str[] names = ["apple", "banana", "tomato"]
|
||||||
}
|
str[3] names2
|
||||||
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
|
|
||||||
|
|
||||||
sys.wait(60)
|
; 8 array assignments -> 8 arraycopies:
|
||||||
changed = false
|
array = [8,7,6]
|
||||||
do {
|
array = array2
|
||||||
sys.waitvsync()
|
flarray = [99.9, 88.8, 77.7]
|
||||||
sys.waitvsync()
|
flarray = flarray2
|
||||||
changed = palette.fade_step_multi(0, 8, $fff)
|
warray = [4444,5555,6666]
|
||||||
} until not changed
|
warray = warray2
|
||||||
sys.wait(60)
|
names = ["x1", "x2", "x3"]
|
||||||
|
names = names2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user