boriel-basic / zxbasic

The Sinclair ZX Spectrum BASIC compiler!
http://zxbasic.net
GNU Affero General Public License v3.0
205 stars 27 forks source link

Incorrect array indexing when passed as parameter #824

Open oskostenko opened 3 weeks ago

oskostenko commented 3 weeks ago

Hi,

Having the following code:

sub PrintArray(arr() as integer)
    dim i as integer
    for i = lbound(arr, 1) to ubound(arr, 1)
        print i; ": "; arr(i)
    next i
end sub

dim arr(1 to 3) as integer => {10, 20, 30}

PrintArray arr

The expected output is:

1: 10
2: 20
3: 30

Actual output:

1: 20
2: 30
3: 0

Is it a bug or a feature?

boriel commented 3 weeks ago

Thanks for reporting. Indeed this is a long known bug: functions are unaware of array dimensions. They will always access the array with the array-base. The solution is not easy (or at least a performant / efficient one). I was thinking on removing the feature of allowing declaring arrays with lower and upper dimensions, and use only number of elements (like in C and other languages), because is a nightmare and a feature seldom used.

Using #pragma array_base=1 or the --array-base=1 command line flag has the same effect and prevents this problem.

Will keep you updated with the fix of this. :-)

oskostenko commented 3 weeks ago

Thank you very much for replying.

functions are unaware of array dimensions

What's the most annoying here, is that lbound and ubound inside the function are aware of array dimensions, and work inconsistently with how the array is actually accessed.

Using #pragma array_base=1 or the --array-base=1 command line flag has the same effect and prevents this problem.

Alas, it doesn't prevent the problem:

#pragma array_base=1

sub PrintArray(arr() as integer)
    dim i as integer
    for i = lbound(arr, 1) to ubound(arr, 1)
        print i; ": "; arr(i)
    next i
end sub

dim arr(3) as integer => {10, 20, 30}

PrintArray arr

Gives the same incorrect output:

1: 20
2: 30
3: 0

I hope this will be fixed soon. To me, 1-based arrays seem very beneficial in Basic, because I find it very awkward to always calculate length - 1 when declaring arrays and iterating over them. Zero-based arrays are absolutely fine in languages like C because of the way they are declared and iterated over, but not in Basic..

boriel commented 2 weeks ago

Yes, but that's because, regardless of the array base functions are totally agnostic of the arrays and dimensions they receive, and operate on base 0. Giving support to these will be very expensive (in terms on memory and speed), but can be done using using the internal LBound and UBound tables. I will try to implement a solution.

In the (long) meantime, you can use lbound to subtract the index if needed. In your case:

#pragma array_base=1

sub PrintArray(arr() as integer)
    dim i as integer
    for i = 0 to ubound(arr, 1) - lbound(arr, 1)
        print i; ": "; arr(i)
    next i
end sub

dim arr(3) as integer => {10, 20, 30}

PrintArray arr

This will work regardless of how the array was declared and the array base. If you want to retrieve the original index, use Lbound(arr, 1) + i

EDIT: Array declarations will be changed in the future 2.x, and all of them will be 0-based or 1-based only. I'm seriously considering removing the DIM a(x1 TO y1, x2 TO y2...) syntax. This will allow a simpler implementation in array access within functions that will operate on the array_base selected (array base can be dynamically changed using #pragma as the example above, so a function can temporarily enforce array_base 0 and restore the previous one upon return, making it compatible to any type of array -i.e. when implementing a library).

boriel commented 4 days ago

Please, consider joining the official telegram channel to discuss this with the community: https://t.me/+ydBiLEzi0_M1OWZk