Request: Slicing array with more than 1 dimension

BlitzMax Forums/BlitzMax Programming/Request: Slicing array with more than 1 dimension

Hi,

I'd request a BlitzMax built-in feature to slice arrays with more than one dimension; it could work like this:
Local Arr [ 5 , 5 ] 'Create a 5x5 array
Arr = Arr [ .. 10 , .. 10 ] 'Extend it to a 10x10 array
Arr = Arr [ 4 .. 6 , 4 .. 6 ] 'Extract the middle four elements in a 2x2 array
Arr = Arr [ -8 .. , -3 .. ] 'Append empty elements to the beginning of the array so that it's 10x5
Arr = Arr [ .. 5 , .. 10 ] 'Skip 25 elements in x-axis(first dimension) and append 25 empty elements in y-axis(second dimension)
This isn't possible yet. I suggest to add this feature; it'll be usefull, because till now you've to create a new array with same dimensions and then copy the elements if you want to do this. I wrote some functions to perform array slices with arrays with up to 5 dimensions:
Strict
Framework brl.blitz

Function ArraySliceInt2D [,] ( arr [,] , dim0start , dim0end , dim1start , dim1end )
  Local dim0size = dim0end - dim0start
  Local dim1size = dim1end - dim1start
  If dim0size <= 0 Or dim1size <= 0
    Return
  EndIf
  Local val [ dim0size , dim1size ]
  Local dims [] = arr.Dimensions ( )
  If Len dims = 2
    Local i0
    For Local dim0index = dim0start Until dim0end
      If dim0index >= 0 And dim0index < dims [ 0 ]
        Local i1
        For Local dim1index = dim1start Until dim1end
          If dim1index >= 0 And dim1index < dims [ 1 ]
            val [ i0 , i1 ] = arr [ dim0index , dim1index ]
          EndIf
          i1 :+ 1
        Next
      EndIf
      i0 :+ 1
    Next
  EndIf
  Return val
EndFunction

Function ArraySliceInt3D [,,] ( arr [,,] , dim0start , dim0end , dim1start , dim1end , dim2start , dim2end )
  Local dim0size = dim0end - dim0start
  Local dim1size = dim1end - dim1start
  Local dim2size = dim2end - dim2start
  If dim0size <= 0 Or dim1size <= 0 Or dim2size <= 0
    Return
  EndIf
  Local val [ dim0size , dim1size , dim2size ]
  Local dims [] = arr.Dimensions ( )
  If Len dims = 3
    Local i0
    For Local dim0index = dim0start Until dim0end
      If dim0index >= 0 And dim0index < dims [ 0 ]
        Local i1
        For Local dim1index = dim1start Until dim1end
          If dim1index >= 0 And dim1index < dims [ 1 ]
            Local i2
            For Local dim2index = dim2start Until dim2end
              If dim2index >= 0 And dim2index < dims [ 2 ]
                val [ i0 , i1 , i2 ] = arr [ dim0index , dim1index , dim2index ]
              EndIf
              i2 :+ 1
            Next
          EndIf
          i1 :+ 1
        Next
      EndIf
      i0 :+ 1
    Next
  EndIf
  Return val
EndFunction

Function ArraySliceInt4D [,,,] ( arr [,,,] , dim0start , dim0end , dim1start , dim1end , dim2start , dim2end , dim3start , dim3end )
  Local dim0size = dim0end - dim0start
  Local dim1size = dim1end - dim1start
  Local dim2size = dim2end - dim2start
  Local dim3size = dim3end - dim3start
  If dim0size <= 0 Or dim1size <= 0 Or dim2size <= 0 Or dim3size <= 0
    Return
  EndIf
  Local val [ dim0size , dim1size , dim2size , dim3size ]
  Local dims [] = arr.Dimensions ( )
  If Len dims = 4
    Local i0
    For Local dim0index = dim0start Until dim0end
      If dim0index >= 0 And dim0index < dims [ 0 ]
        Local i1
        For Local dim1index = dim1start Until dim1end
          If dim1index >= 0 And dim1index < dims [ 1 ]
            Local i2
            For Local dim2index = dim2start Until dim2end
              If dim2index >= 0 And dim2index < dims [ 2 ]
                Local i3
                For Local dim3index = dim3start Until dim3end
                  If dim3index >= 0 And dim3index < dims [ 3 ]
                    val [ i0 , i1 , i2 , i3 ] = arr [ dim0index , dim1index , dim2index , dim3index ]
                  EndIf
                  i3 :+ 1
                Next
              EndIf
              i2 :+ 1
            Next
          EndIf
          i1 :+ 1
        Next
      EndIf
      i0 :+ 1
    Next
  EndIf
  Return val
EndFunction

Function ArraySliceInt5D [,,,,] ( arr [,,,,] , dim0start , dim0end , dim1start , dim1end , dim2start , dim2end , dim3start , dim3end , dim4start , dim4end )
  Local dim0size = dim0end - dim0start
  Local dim1size = dim1end - dim1start
  Local dim2size = dim2end - dim2start
  Local dim3size = dim3end - dim3start
  Local dim4size = dim4end - dim4start
  If dim0size <= 0 Or dim1size <= 0 Or dim2size <= 0 Or dim3size <= 0 Or dim4size <= 0
    Return
  EndIf
  Local val [ dim0size , dim1size , dim2size , dim3size , dim4size ]
  Local dims [] = arr.Dimensions ( )
  If Len dims = 5
    Local i0
    For Local dim0index = dim0start Until dim0end
      If dim0index >= 0 And dim0index < dims [ 0 ]
        Local i1
        For Local dim1index = dim1start Until dim1end
          If dim1index >= 0 And dim1index < dims [ 1 ]
            Local i2
            For Local dim2index = dim2start Until dim2end
              If dim2index >= 0 And dim2index < dims [ 2 ]
                Local i3
                For Local dim3index = dim3start Until dim3end
                  If dim3index >= 0 And dim3index < dims [ 3 ]
                    Local i4
                    For Local dim4index = dim4start Until dim4end
                      If dim4index >= 0 And dim4index < dims [ 4 ]
                        val [ i0 , i1 , i2 , i3 , i4 ] = arr [ dim0index , dim1index , dim2index , dim3index , dim4index ]
                      EndIf
                      i4 :+ 1
                    Next
                  EndIf
                  i3 :+ 1
                Next
              EndIf
              i2 :+ 1
            Next
          EndIf
          i1 :+ 1
        Next
      EndIf
      i0 :+ 1
    Next
  EndIf
  Return val
EndFunction

Could you implement this as BlitzMax built-in feature? That would be really nice.

It is possible.

If you want to slice with more than 1 dimension, you can use Array of Arrays.

Your idea, while nice, won't work, because it needs to be programmed for any amount of dimensions not only a fixed amount.

Yes, I know that just writing more of these functions won't solve the problem; so I wrote a new one, which works with each number of dimensions. I also added an array concat function, but for now I'm mainly requesting the slice function to be implemented:
ArraySliceEx.c
#include <brl.mod/blitz.mod/blitz.h>

int hasIndex(int *scales,int dims,int *indexarea){
	int count=dims;
	while(count){
		--count;
		if(indexarea[count]<0||indexarea[count]>=scales[count])return 0;
	}
	return 1;
}

int calcIndex(int *scales,int dims,int el_size,int *indexarea){
	int count=0;
	int index=0;
	while(count<dims){
		index*=scales[count];
		index+=indexarea[count];
		++count;
	}
	return index*el_size;
}

int calcIndexEx(int *param,int dims,int el_size,int *indexarea){
	int count=0;
	int index=0;
	while(count<dims){
		index*=param[count*2+1]-param[count*2];
		index+=indexarea[count]-param[count*2];
		++count;
	}
	return index*el_size;
}

void arrayProc(void *datato,void *datafrom,int *param,int *scales,int *indexarea,void *init,int el_size,int obj,int dims,int cur){
	if(dims==cur){
		int curindex=calcIndexEx(param,dims,el_size,indexarea);
		if(hasIndex(scales,dims,indexarea)){
			memcpy(datato+curindex,datafrom+calcIndex(scales,dims,el_size,indexarea),el_size);
			if(obj){BBRETAIN((BBObject*)(datato+curindex));}
		}else{
			if(init){
				*((void**)(datato+curindex))=init;
			}else{
				memset(datato+curindex,0,el_size);
			}
		}
	}else{
		indexarea[cur]=param[cur*2];
		while(indexarea[cur]<param[cur*2+1]){
			arrayProc(datato,datafrom,param,scales,indexarea,init,el_size,obj,dims,cur+1);
			indexarea[cur]++;
		}
	}
}

BBArray *bbArraySliceEx( const char *type,BBArray *inarr,int dims,... ){
	int *param=((int*)(&dims))+1;
	int *scales=(int*)malloc(dims*8);

	int size=1;
	int count=dims;
	while(count){
		--count;
		scales[count]=param[count*2+1]-param[count*2];
		if(scales[count]<=0)return &bbEmptyArray;
		size*=scales[count];
	}
	void *init=0;
	int el_size=4;
	switch( type[0] ){
	case 'b':el_size=1;break;
	case 's':el_size=2;break;
	case 'l':el_size=8;break;
	case 'd':el_size=8;break;
	case ':':init=&bbNullObject;break;
	case '$':init=&bbEmptyString;break;
	case '[':init=&bbEmptyArray;break;
	case '(':init=&brl_blitz_NullFunctionError;break;
	}
	size*=el_size;
	BBArray *arr=(BBArray*)bbGCAlloc( BBARRAYSIZE(size,dims),(BBGCPool*)&bbArrayClass );
	arr->type=type;
	arr->dims=dims;
	arr->size=size;
	memcpy((void*)(arr->scales),(void*)scales,4*dims);
	if(inarr->dims==dims){
		count=dims-1;
		scales[dims+count]=inarr->scales[count];
		while(count){--count;scales[dims+count]=inarr->scales[count]/inarr->scales[count+1];}
		arrayProc(BBARRAYDATA(arr,dims),BBARRAYDATA(inarr,dims),param,scales+dims,
		scales,init,el_size,type[0]==':'||type[0]=='$'||type[0]=='[',dims,0);
	}else{
		void **p=(void**)BBARRAYDATA(arr,dims);
		if( init ){
			count=size/4;
			while(count){--count;p[count]=init;}
		}else{
			memset( (void*)p,0,size );
		}
	}
	free((void*)(scales));
	count=dims-1;
	while(count){--count;arr->scales[count]*=arr->scales[count+1];}
	return arr;
}

ArrayConcat.c:
#include <brl.mod/blitz.mod/blitz.h>

BBArray *CallFunction( void *func,void *param,int paramlen );
void bbArraySliceEx();

BBArray *bbArrayConcat( const char *type,int dims,BBArray *x,BBArray *y){
	int m=(dims!=x->dims);
	int n=(dims!=y->dims);
	if(m&&n)return &bbEmptyArray;
	if(m)return y;
	if(n)return x;
	int length=12+dims*8;
	int *mem=(int*)malloc(length);
	int cx=0;
	int cy=0;
	int count=dims;
	while(count>1){
		--count;
		m=(x->scales[count]);
		n=(y->scales[count]);
		if(count<dims-1){m/=(x->scales[count+1]);n/=(y->scales[count+1]);}
		if(m<n){
			m=n;
			cx=1;
		}else if(m>n){
			cy=1;
		}
		mem[3+count*2]=0;
		mem[4+count*2]=m;
	}
	mem[0]=(int)type;
	if(cx||cy){
		mem[2]=dims;
		mem[3]=0;
		if(cx){
			mem[1]=x;
			mem[4]=(x->scales[0]);
			if(dims>1)mem[4]/=(x->scales[1]);
			x=CallFunction(bbArraySliceEx,mem,length);
		}
		if(cy){
			mem[1]=y;
			mem[4]=(y->scales[0]);
			if(dims>1)mem[4]/=(y->scales[1]);
			y=CallFunction(bbArraySliceEx,mem,length);
		}
	}
	mem[1]=dims;
	m=(x->scales[0]);
	n=(y->scales[0]);
	if(dims>1){m/=(x->scales[1]);n/=(y->scales[1]);}
	mem[2]=m+n;
	while(count<dims){
		mem[2+count]=mem[4+count*2];
		++count;
	}
	BBArray *arr=CallFunction(bbArrayNew,mem,8+dims*4);
	free((void*)mem);
	mem=(int*)BBARRAYDATA(arr,dims);
	m=(x->size);
	memcpy((void*)mem,BBARRAYDATA(x,dims),m);
	memcpy(((void*)mem)+m,BBARRAYDATA(y,dims),y->size);
	if( type[0]==':' || type[0]=='$' || type[0]=='[' ){
		count=(arr->size)/4;
		while(count){
			--count;
			BBRETAIN(((BBObject**)mem)[count]);
		}
	}
	return arr;
}

CallFunction.s:
format MS COFF
public _CallFunction
section "code" code

;dword[esp]    - Return address
;dword[esp+4]  - The function to call
;dword[esp+8]  - Pointer to the parameters
;dword[esp+12] - Parameter length in bytes
_CallFunction:
push ebp
mov ebp,esp
push ebx
push ecx
mov ebx,dword[ebp+12]
mov ecx,dword[ebp+16]
add ebx,ecx
_begin_loop:
cmp ecx,0
je _end_loop
sub ebx,1
sub ecx,1
sub esp,1
mov al,byte[ebx]
mov byte[esp],al
jmp _begin_loop
_end_loop:
mov eax,0
call dword[ebp+8]
mov esp,ebp
sub esp,8
pop ecx
pop ebx
pop ebp
ret



Here's an example:
Example.bmx:
Strict
Framework brl.blitz

Import "ArraySliceEx.c"
Import "ArrayConcat.c"
Import "CallFunction.s"

Global t:Byte Ptr = "$".ToCString ( )

Local Arr3x3$ [ 3 , 3 ] 'Create a two dimensional 3x3 array

Arr3x3 [ 0 , 0 ] = "Ar"
Arr3x3 [ 0 , 1 ] = "ra"
Arr3x3 [ 0 , 2 ] = "y:" 'Fill some values into the array
Arr3x3 [ 1 , 0 ] = "He"
Arr3x3 [ 1 , 1 ] = "ll"
Arr3x3 [ 1 , 2 ] = "o,"
Arr3x3 [ 2 , 0 ] = "wo"
Arr3x3 [ 2 , 1 ] = "rl"
Arr3x3 [ 2 , 2 ] = "d!"

WriteStdout "Let's start with this array:~n"
PrintArr2D Arr3x3 'Print array contents

Local Arr2x6$ [,] = bbArraySliceEx ( t , Arr3x3 , 2 , 1 , 3 , -2 , 4 )
'If this feature would be implemented
'in the compiler you could write:
'Local Arr2x6 [,] = Arr3x3 [ 1 .. 3 , -2 .. 4 ]

WriteStdout "After slicing the array it is:~n"
PrintArr2D Arr2x6 'Print array contents

Local Arr5x5$ [,] = bbArrayConcat ( t , 2 , Arr2x6 , Arr3x3 )
'If this feature would be implemented
'in the compiler you could write:
'Local Arr5x5 [,] = Arr2x6 + Arr3x3

WriteStdout "After array concat with the first one:~n"
PrintArr2D Arr5x5 'Print array contents

Function PrintArr2D ( arr$ [,] )
  Local dims [] = arr.Dimensions ( )
  If Len dims = 2
    For Local x = 0 Until dims [ 0 ]
      For Local y = 0 Until dims [ 1 ]
        WriteStdout "[" + arr [ x , y ] [.. 2 ] + "]"
      Next
      WriteStdout "~n"
    Next
  EndIf
EndFunction

Extern
  Function bbArraySliceEx$ [,] ( t:Byte Ptr , arr$ [,] , dims , beg0 , end0 , beg1 , end1 )
  Function bbArrayConcat$ [,] ( t:Byte Ptr , dims , x$ [,] , y$ [,] )
EndExtern

Such a feature would be really nice, for example when writing a high score list as array, where you sometimes need to insert a high score entry between two others - just part the array using slices and then put it all together using concat.
There're lots of situations, where this feature would be really useful.

I wrote another example showing where this feature would be very useful. The example is creating a customized command prompt, storing all entered characters in a two dimensional array. Things like scrolling up the display can easily be done by slicing and concating the array. If I want to do something like this, till now, I have to write a function myself which is doing what I want to do. So I request to implement this to BlitzMax, I think it would be useful for many blitz users.
Command.bmx:
Strict
Framework brl.blitz

Import brl.timer
Import brl.glmax2d
Import brl.d3d7max2d
Import pub.freeprocess

Import "ArraySliceEx.c"
Import "ArrayConcat.c"
Import "CallFunction.s"

Global T:Byte Ptr = "i".ToCString ( )
AppTitle = "Command prompt"
Graphics 1 , 1
Global WrittenX
Global WrittenY
Global Stop
Global CursorX
Global CursorY
Global TextW = TextWidth ( " " )
Global TextH = TextHeight ( " " )
Global CW = 62
Global CH = 17
Global Chars [ CH , CW ]
Graphics CW * TextW , CH * TextH
AddHook EmitEventHook , Hook
CreateTimer 20
Global Process:TProcess = TProcess.Create ( "cmd" , 1 )
While Not Stop
  WaitSystem
Wend
TerminateProcess Process

Function Hook:Object ( ID , Data:Object , Context:Object )
  Local Event:TEvent = TEvent ( Data )
  Select Event.id
    Case EVENT_APPTERMINATE
      Stop = True
    Case EVENT_TIMERTICK
      Cls
      For Local X = 0 Until CW
        For Local Y = 0 Until CH
          DrawText Chr Chars [ Y , X ] , X * TextW , Y * TextH
        Next
      Next
      DrawText "_" , CursorX * TextW , CursorY * TextH
      Flip 0
      Local Data:Byte [ Process.pipe.ReadAvail ( ) ]
      If Data
        Process.pipe.Read Data , Len Data
        DisplayWrite Data
      EndIf
      If Not Process.Status ( )
        Stop = True
      EndIf
    Case EVENT_KEYDOWN
      Select Event.data
        Case KEY_RETURN
          Local Data:Byte [] = DisplayRead ( )
          Process.pipe.Write Data , Len Data
          Process.pipe.Write ( [ 10:Byte ] , 1 )
        Case KEY_LEFT
          CursorX :- 1
          If CursorX = -1
            CursorX = CW - 1
            CursorY :- 1
            If CursorY = -1
              CursorY = 0
            EndIf
          EndIf
        Case KEY_RIGHT
          CursorX :+ 1
          If CursorX = CW
            CursorX = 0
            CursorY :+ 1
            If CursorY = CH
              CursorY = CH - 1
            EndIf
          EndIf
        Case KEY_UP
          CursorY :- 1
          If CursorY = -1
            CursorY = 0
          EndIf
        Case KEY_DOWN
          CursorY :+ 1
          If CursorY = CH
            CursorY = CH - 1
          EndIf
      EndSelect
    Case EVENT_KEYCHAR
      Select Event.data
        Case KEY_ESCAPE
          Stop = True
        Case KEY_RETURN
          Local Part1 [,] = bbArraySliceEx ( T , Chars , 2 , 0 , CursorY , 0 , CW ) 'We need to part the display's contents into four parts to provide a powerful line break
          Local Part2 [,] = bbArraySliceEx ( T , Chars , 2 , CursorY + 1 , CH , 0 , CW )
          Chars = bbArraySliceEx ( T , Chars , 2 , CursorY , CursorY + 1 , 0 , CW )
          Local Part3 [,] = bbArraySliceEx ( T , Chars , 2 , 0 , 1 , 0 , CursorX )
          Local Part4 [,] = bbArraySliceEx ( T , Chars , 2 , 0 , 1 , CursorX , CW )
          Chars = Part1
          Chars = bbArrayConcat ( T , 2 , Chars , Part3 ) 'Put all parts together, but in a different order
          Chars = bbArrayConcat ( T , 2 , Chars , Part4 )
          Chars = bbArrayConcat ( T , 2 , Chars , Part2 )
          CursorX = 0
          CursorY :+ 1
          If CursorY = CH
            Chars = bbArraySliceEx ( T , Chars , 2 , 1 , CH + 1 , 0 , CW ) 'This code scrolls the display contents
            CursorY = CH - 1
            WrittenY :- 1
            If WrittenY = -1
              WrittenY = 0
            EndIf
          Else
            Chars = bbArraySliceEx ( T , Chars , 2 , 0 , CH , 0 , CW ) 'Ensure that the array stays at the correct size
          EndIf
        Case KEY_BACKSPACE
          CursorX :- 1
          If CursorX = -1
            CursorX = CW - 1
            CursorY :- 1
            If CursorY = -1
              CursorY = 0
            EndIf
          EndIf
          Chars [ CursorY , CursorX ] = 0
        Case 10
        Default
          Chars [ CursorY , CursorX ] = Event.data
          CursorX :+ 1
          If CursorX = CW
            CursorX = 0
            CursorY :+ 1
            If CursorY = CH
              Chars = bbArraySliceEx ( T , Chars , 2 , 1 , CH + 1 , 0 , CW ) 'This code scrolls all characters in the command prompt's display
              CursorY = CH - 1
              WrittenY :- 1
              If WrittenY = -1
                WrittenY = 0
              EndIf
            EndIf
          EndIf
      EndSelect
  EndSelect
  Return Data
EndFunction

Function DisplayWrite ( Data:Byte [] )
  While Data
    TEvent.Create ( EVENT_KEYCHAR , Null , Data [ 0 ] ).Emit
    Data = Data [ 1 ..]
  Wend
  WrittenX = CursorX
  WrittenY = CursorY
EndFunction

Function DisplayRead:Byte [] ( )
  Local ReadBeg = WrittenX + WrittenY * CW
  Local ReadEnd = CursorX + CursorY * CW
  Local Arr:Byte [ ReadEnd - ReadBeg ]
  While ReadBeg < ReadEnd
    Arr [ Len Arr + ReadBeg - ReadEnd ] = ( Int Ptr Chars ) [ ReadBeg ]
    ReadBeg :+ 1
  Wend
  WrittenX = CursorX
  WrittenY = CursorY
  Return Arr
EndFunction

Extern
  Function bbArraySliceEx [,] ( t:Byte Ptr , arr [,] , dims , beg0 , end0 , beg1 , end1 )
  Function bbArrayConcat [,] ( t:Byte Ptr , dims , x [,] , y [,] )
EndExtern

You can see that there're many extern function calls to slice or concat the array. A BlitzMax built-in feature would make this much more readable and easier for the programmer to write it.

Simply using the already present technology instead of insisting on reinventing the wheel would help as well and you wouldn't need a single line extern.


int[][] is sliceable and has scalable sub array sizes instead of a fixed size for each "row" as additional powerfull feature.

Of course there're many ways to work around this. So creating an array of arrays could also solve the problem, I know.
But the actual reason why I request this is that there're some situations where this would be the problems best solution, because you have to represent a two (or any other) dimensional thing in the memory; for example the pixels in a screen or the characters in a command prompt (as the example shows).
So I think it would be the best to have something like a two dimensional array.... err what? This already exists! So I don't have to invent the wheel - multi dimensional arrays are already implemented to BlitzMax. The only problem is that we currently don't have the useful functions to effectively work with them.
I simply request a possibility for better using the features already supported.

Arrays of arrays:
Sure they're sometimes useful, but not for properly solving the problem I mentioned; just because in those cases it's even a risk to have different scales along one of the dimensions (pixels of a screen). Of course it would be possible to go through all the source and make sure that these sub arrays always have the same size, but even the best programmer sometimes makes mistakes, so it's a bit a task of the programming language to notice the programmer if something is done totally wrong.
I know there're many cases where int[][] is needed and useful, but those are not these I'm talking about, so in this case int[,] would be better.
Another disadvantage of that way to solve the problem is that arrays of arrays have a big overhead, you'd always have an array just to store references to the others and each of those has bit header storing information about the array class, reference count, element type, total size, number of dimensions (which is always 1) and so on... wasted memory.

I know, int[][] works, but that's a normal behaviour of a workaround - the much better solution is a two dimensional array.

No, using Array of Array is no workaround.

You are using it, if you either need a dynamic second dimension (unlike multi dim arrays which have fixed sizes for the second dimension) or if you need slicing support.

Thats no workaround but the simple way it is. If you want to have it the "cheap way" (although it is not really cheaper to have a multi dim array, why? A multidim is a single dim with "mod /" support internally. Instead of int[2,5] you could write int[10] and i = x + y * 5 when accessing it), then thats the only way to go with multidim.

You are using it, if you either need a dynamic second dimension (unlike multi dim arrays which have fixed sizes for the second dimension) or if you need slicing support.
That's a recursion. In BlitzMax till now array slicing is only allowed with 1D-arrays. That's why we have to use them. So saying "use them, because you need to use them" isn't what I was asking for. In fact I'm currently using arrays of arrays, but this isn't the proper solution for a problem where a fixed second dimension is needed. Yes, sometimes a dynamic second dimension isn't an advantage, because in these cases (as posted above: pixels in screen, characters in command prompt...) you need to have a fixed second dimension.
So in such situations a 2D array would be the best solution. And as I already said multi dimensional arrays are available in BlitzMax, so why not use them if you need them?
It's just that we would need some features for better handling them -> array slicing/concating.

Of course there's always a way to do something without this feature, but I didn't said "I'm having a ploblem/how to do this?" - of course there's a way to solve my problem till now, and I know this way, but I just think that the language gets more self contained with this feature.

that might be, it would add another feature to it.

Main problem is that it won't work with multi dim as users are used to it with regular arrays.
For that reason it might be added or might be not, we will see if your code is part of a future syncmod or BM 1.24 :)
It surely wouldn't hurt.

But until then, there is at least the possibility to use an array of array which has no further disadvantage over multi dim array than having multiple [] instead of only one :) (oh and naturaly the 20min play around until one understands how initialisation and slicing works)