don't allow problematic string and array assignments anymore, improve error messages.

In certain cases you will need to use string.copy() explicitly to overwrite strings with new strings.
This commit is contained in:
Irmen de Jong
2024-10-09 00:51:05 +02:00
parent eaa22a9d13
commit 5731b79554
6 changed files with 51 additions and 60 deletions

View File

@@ -2,6 +2,8 @@
compression { compression {
%option no_symbol_prefixing, ignore_unused
sub encode_rle_outfunc(uword data, uword size, uword output_function, bool is_last_block) { sub encode_rle_outfunc(uword data, uword size, uword output_function, bool is_last_block) {
; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding. ; -- Compress the given data block using ByteRun1 aka PackBits RLE encoding.
; output_function = address of a routine that gets a byte arg in A, ; output_function = address of a routine that gets a byte arg in A,

View File

@@ -550,10 +550,8 @@ internal class AstChecker(private val program: Program,
fun checkType(target: AssignTarget, value: Expression, augmentable: Boolean) { fun checkType(target: AssignTarget, value: Expression, augmentable: Boolean) {
val targetDt = target.inferType(program) val targetDt = target.inferType(program)
val valueDt = value.inferType(program) val valueDt = value.inferType(program)
if(valueDt.isKnown && !(valueDt isAssignableTo targetDt)) { if(valueDt.isKnown && !(valueDt isAssignableTo targetDt) && !targetDt.isIterable) {
if(targetDt.isIterable) if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
errors.err("cannot assign value to string or array", value.position)
else if(!(valueDt istype DataType.STR && targetDt istype DataType.UWORD)) {
if(targetDt.isUnknown) { if(targetDt.isUnknown) {
if(target.identifier?.targetStatement(program)!=null) if(target.identifier?.targetStatement(program)!=null)
errors.err("target datatype is unknown", target.position) errors.err("target datatype is unknown", target.position)
@@ -1844,6 +1842,12 @@ internal class AstChecker(private val program: Program,
else if(targetDatatype==DataType.BOOL && sourceDatatype!=DataType.BOOL) { else if(targetDatatype==DataType.BOOL && sourceDatatype!=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)
} }
else if(targetDatatype==DataType.STR) {
if(sourceDatatype==DataType.UWORD)
errors.err("can't assign UWORD to STR. If the source is a string and you actually want to overwrite the target string, use an explicit string.copy(src,tgt) instead.", position)
else
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
}
else { else {
errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position) errors.err("type of value $sourceDatatype doesn't match target $targetDatatype", position)
} }

View File

@@ -210,15 +210,11 @@ internal class StatementReorderer(
val targetType = assignment.target.inferType(program) val targetType = assignment.target.inferType(program)
if(targetType.isArray && valueType.isArray) { if(targetType.isArray && valueType.isArray) {
if (assignment.value is ArrayLiteral) { checkCopyArrayValue(assignment)
errors.err("cannot assign array literal here, use separate assignment per element", assignment.position)
} else {
return copyArrayValue(assignment)
}
} }
if(!assignment.isAugmentable) { if(!assignment.isAugmentable) {
if (valueType.isString && (targetType istype DataType.STR || targetType istype DataType.ARRAY_B || targetType istype DataType.ARRAY_UB)) { if (valueType istype DataType.STR && targetType istype DataType.STR) {
// replace string assignment by a call to stringcopy // replace string assignment by a call to stringcopy
return copyStringValue(assignment) return copyStringValue(assignment)
} }
@@ -247,18 +243,22 @@ internal class StatementReorderer(
return noModifications return noModifications
} }
private fun copyArrayValue(assign: Assignment): List<IAstModification> { private fun checkCopyArrayValue(assign: Assignment) {
val identifier = assign.target.identifier!! val identifier = assign.target.identifier!!
val targetVar = identifier.targetVarDecl(program)!! val targetVar = identifier.targetVarDecl(program)!!
if(targetVar.arraysize==null) { if(targetVar.arraysize==null) {
errors.err("array has no defined size", assign.position) errors.err("array has no defined size", assign.position)
return noModifications return
}
if(assign.value is ArrayLiteral) {
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) errors.err("invalid array value to assign to other array", assign.value.position)
return noModifications return
} }
val sourceIdent = assign.value as IdentifierReference val sourceIdent = assign.value as IdentifierReference
val sourceVar = sourceIdent.targetVarDecl(program)!! val sourceVar = sourceIdent.targetVarDecl(program)!!
@@ -273,7 +273,6 @@ internal class StatementReorderer(
errors.err("element size mismatch", assign.position) errors.err("element size mismatch", assign.position)
} }
} }
return noModifications
} }
private fun copyStringValue(assign: Assignment): List<IAstModification> { private fun copyStringValue(assign: Assignment): List<IAstModification> {

View File

@@ -286,8 +286,7 @@ Arrays
^^^^^^ ^^^^^^
Array types are also supported. They can be formed from a list of booleans, bytes, words, floats, or addresses of other variables Array types are also supported. They can be formed from a list of booleans, bytes, words, floats, or addresses of other variables
(such as explicit address-of expressions, strings, or other array variables) - values in an array literal (such as explicit address-of expressions, strings, or other array variables) - values in an array literal
always have to be constants. Putting variables inside an array has to be done on a value-by-value basis. always have to be constants. Here are some examples of arrays::
Here are some examples of arrays::
byte[10] array ; array of 10 bytes, initially set to 0 byte[10] array ; array of 10 bytes, initially set to 0
byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value byte[] array = [1, 2, 3, 4] ; initialize the array, size taken from value
@@ -300,6 +299,7 @@ Here are some examples of arrays::
value = array[3] ; the fourth value in the array (index is 0-based) value = array[3] ; the fourth value in the array (index is 0-based)
char = string[4] ; the fifth character (=byte) in the string char = string[4] ; the fifth character (=byte) in the string
char = string[-2] ; the second-to-last character in the string (Python-style indexing from the end) char = string[-2] ; the second-to-last character in the string (Python-style indexing from the end)
flags = [false, true] ; reset all flags in the array
.. note:: .. note::
Right now, the array should be small enough to be indexable by a single byte index. Right now, the array should be small enough to be indexable by a single byte index.
@@ -312,9 +312,8 @@ 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's possible to assign a new array to another array, this will overwrite all elements in the original 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 value array. The number and types of elements 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 every element is copied over. It should probably be avoided.
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,
@@ -650,10 +649,6 @@ Assignment statements assign a single value to a target variable or memory locat
Augmented assignments (such as ``aa += xx``) are also available, but these are just shorthands Augmented assignments (such as ``aa += xx``) are also available, but these are just shorthands
for normal assignments (``aa = aa + xx``). for normal assignments (``aa = aa + xx``).
Only variables of type byte, word and float can be assigned a new value.
It's not possible to set a new value to string or array variables etc, because they get allocated
a fixed amount of memory which will not change. (You *can* change the value of elements in a string or array though).
It is possible to "chain" assignments: ``x = y = z = 42``, this is just a shorthand It is possible to "chain" assignments: ``x = y = z = 42``, this is just a shorthand
for the three individual assignments with the same value 42. for the three individual assignments with the same value 42.

View File

@@ -1,6 +1,12 @@
TODO TODO
==== ====
Don't allow assigning str to array!
Don't allow assigning array to str!
Don't allow assigning a word to an array or string!
Put palette fade to white / black in.
Regenerate skeleton doc files. Regenerate skeleton doc files.
Improve register load order in subroutine call args assignments: Improve register load order in subroutine call args assignments:
@@ -12,6 +18,7 @@ Maybe this routine can be made more intelligent. See usesOtherRegistersWhileEva
Future Things and Ideas Future Things and Ideas
^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^
- The string assignment footgun should be removed in favor of just calling string.copy explicitly. Get rid of sys.internal_stringcopy asm routine. Fix docs too.
- 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?)

View File

@@ -1,46 +1,30 @@
%import textio %import textio
%import string
%zeropage basicsafe %zeropage basicsafe
main { main {
sub start() { sub start() {
routine(11,22,33) str name1 = "irmen"
txt.nl() str name2 = "other"
cx16.r0 = callfar2(0, &routine, 11, 22, 33, true) bool[2] flags = [true, false]
txt.nl()
txt.print_uwhex(cx16.r0, true)
txt.nl()
cx16.r0 = callfar(0, &routine, 11*256 + 22)
txt.nl()
txt.print_uwhex(cx16.r0, true)
txt.nl()
}
asmsub routine(ubyte v1 @A, ubyte v2 @X, ubyte v3 @Y) -> uword @AY { txt.print(name1)
%asm {{ txt.nl()
sta cx16.r8L name1 = name2
stx cx16.r9L txt.print(name1)
sty cx16.r10L txt.nl()
lda #0 flags = [false, true]
rol a
sta cx16.r11L
lda cx16.r8L ubyte[10] array
jsr txt.print_ub ubyte[10] array2
lda #' '
jsr txt.chrout void string.copy(name2, name1)
lda cx16.r9L array = array2
jsr txt.print_ub name2 = "zzz"
lda #' ' array = [1,2,3,4,5,6,7,8,9,10]
jsr txt.chrout ;; array = cx16.r0
lda cx16.r10L ;; array = name1
jsr txt.print_ub ;; name1 = array
lda #' ' ;; name1 = cx16.r0
jsr txt.chrout
lda cx16.r11L
jsr txt.print_ub
lda #$31
ldy #$ea
rts
}}
} }
} }