SRL Archive - Lape

Untyped variables: Untyped array and letting lape know it's an array?


Gu3st...

2014-03-23 18:30:14

Can someone tell me more about how lape's untyped variable works? @Dgby714;? @Brandon;?

What I'm wanting to find out is if lape can recognize that the untyped variable is an array of any sort.

If there's any possible way to make lape recognize this then creating i.e a generic quicksort function is trivial, which would be pretty nice :)

riwu

2014-03-23 18:53:48

We happened to have a discussion on untyped and creating generic functions for all types at http://villavu.com/forum/showthread.php?t=68613

nielsie said untyped parameter does not have type info http://villavu.com/forum/showthread.php?t=68613&page=5&p=1265968#post1265968

Gu3st...

2014-03-23 19:28:05

Thanks, riwu :) rep+

Moving on to the next question: Is it possible to define a pointer so lape knows it's pointing to an array?
E: AFAIK pointers doesn't work that way, but you never know if someone else has some sort of hack

Gu3st...

2014-03-23 20:33:26

This is the most generic quicksort function I've been able to create so far…
Anyone have any remarks on it, feel free to comment. :)

{
// Use these functions to utilize TPointerArray.quickSort:
// Replace "(*EDITME*)" with the name of your type.
function T(*EDITME*)Array.toPointerArray(): TPointerArray; overload;
var
I : Integer;
begin
setLength(result, length(self));
for I := 0 to high(result) do
result[I] := @self[I];
end;

procedure T(*EDITME*)Array.fromPointerArray(const pointerArr : TPointerArray);
var
I : Integer;
begin
setLength(self, length(x));
for I := 0 to high(self) do
self[I] := pointerArr[I]^;
end;

procedure T(*EDITME*)Array.quickSort(const distArr : TExtendedArray; lowIndex, highIndex : Integer = -1; const sortUp : Boolean = True);
var
tmpSelfArr := copy(self);
tmpPointerArr := tmpSelfArr.toPointerArray();
begin
if lowIndex = -1 then lowIndex := 0;
if highIndex = -1 then highIndex := high(self);
tmpPointerArr.quickSort(distArr, lowIndex, highIndex, sortUp);
self.fromPointerArray(tmpPointerArr);
end;
}

type
TPointerArray = array of Pointer;

procedure TPointerArray.quickSort(distArr : TExtendedArray;
const lowIndex, highIndex : Integer; const sortUp : Boolean);
var
iLow := lowIndex;
iHigh := highIndex;
midDist := distArr[(iLow + iHigh) shr 1];
begin
if length(self) <> length(distArr) then exit();

repeat
if sortUp then
begin
while DistArr[iLow] < midDist do inc(iLow);
while DistArr[iHigh] > midDist do dec(iHigh);
end else
begin
while DistArr[iLow] > midDist do inc(iLow);
while DistArr[iHigh] < midDist do dec(iHigh);
end;

if iLow <= iHigh then
begin
swap(DistArr[iLow], DistArr[iHigh]);
swap(self[iLow], self[iHigh]);

inc(iLow);
dec(iHigh);
end;
until (iLow > iHigh);
if iHigh > lowIndex then self.quickSort(DistArr, lowIndex, iHigh, sortUp);
if iLow < highIndex then self.quickSort(DistArr, iLow, highIndex, sortUp);
end;

// Our test script with a random test type:
type
TTest = record
I : Integer;
end;
TTestArray = array of TTest;

function test(const I : Integer): TTest;
begin
result.I := I;
end;

//************************
// These 3 functions are copied straight from the above comment,
// with (*EDITME*) replaced with Test
function TTestArray.toPointerArray(): TPointerArray;
var
I : Integer;
begin
setLength(result, length(self));
for I := 0 to high(result) do
result[I] := @self[I];
end;

procedure TTestArray.fromPointerArray(const pointerArr : TPointerArray);
var
I : Integer;
begin
setLength(self, length(pointerArr));
for I := 0 to high(pointerArr) do
self[I] := pointerArr[I]^;
end;

procedure TTestArray.quickSort(const distArr : TExtendedArray; lowIndex, highIndex : Integer = -1; const sortUp : Boolean = True);
var
tmpSelfArr := copy(self);
tmpPointerArr := tmpSelfArr.toPointerArray();
begin
if lowIndex = -1 then lowIndex := 0;
if highIndex = -1 then highIndex := high(self);
tmpPointerArr.quickSort(distArr, lowIndex, highIndex, sortUp);
self.fromPointerArray(tmpPointerArr);
end;
//************************

procedure TTestArray.new(const I : Integer; index : Integer = -1);
begin
if index = -1 then index := length(self);
insert(test(I), self, index);
end;

var
testArr : TTestArray;

begin
testArr.new(1);
testArr.new(3);
testArr.new(4);
testArr.new(5);
testArr.new(2);
writeLn(testArr);
testArr.quickSort([1.0, 3.0, 4.0, 5.0, 2.0]);
writeLn(testArr);
end;

E: It just feels so counter-productive to be able to have a couple of type-functions that are 100% the same code with only the type changed work, but not a couple of functions that works on all types…

Brandon

2014-03-23 21:19:08

From: Zyt3x
Thanks, riwu :) rep+

Moving on to the next question: Is it possible to define a pointer so lape knows it's pointing to an array?
E: AFAIK pointers doesn't work that way, but you never know if someone else has some sort of hack


I use the following code in my plugins.. Porting it to lape, it works fairly well..


(*
template<typename T>
int PascalHigh(T* Arr)
{
return reinterpret_cast<const int*>(Arr)[-1] - 1;
}

template<typename T>
int PascalLength(T* Arr)
{
return reinterpret_cast<const int*>(Arr)[-1];
}

template<typename T>
int PascalReferenceCount(T* Arr)
{
return reinterpret_cast<const int*>(Arr)[-2];
}
*)

Function Hack_High(Ptr: Pointer): UInt32;
type
PUInt32 = ^UInt32;
begin
Result := (PUInt32(Ptr) - sizeof(UInt32))^;
end;

Function Hack_Length(Ptr: Pointer): UInt32;
begin
Result := Hack_High(Ptr) + 1;
end;

Function Hack_ReferenceCount(Ptr: Pointer): Integer;
type
PInt32 = ^Int32;
begin
Result := (PInt32(Ptr) - (sizeof(Int32) * 2))^;
end;

Function IsArrayPointer(Ptr: Pointer): Boolean;
begin
Result := (Hack_ReferenceCount(Ptr) > 0);
end;

var
Arr: TIntegerArray;
I: Integer;
S: String;
begin
SetLength(Arr, 10);
S := 'Hello';
writeln(IsArrayPointer(@I)); //No.. obvious.
writeln(IsArrayPointer(@Arr[0])); //Yes.. obvious.
writeln(IsArrayPointer(@S[1])); //Yes.. strings are char arrays.
end.

Gu3st...

2014-03-23 21:35:05

From: Brandon
I use the following code in my plugins.. Porting it to lape, it works fairly well..


(*
template<typename T>
int PascalHigh(T* Arr)
{
return reinterpret_cast<const int*>(Arr)[-1] - 1;
}

template<typename T>
int PascalLength(T* Arr)
{
return reinterpret_cast<const int*>(Arr)[-1];
}

template<typename T>
int PascalReferenceCount(T* Arr)
{
return reinterpret_cast<const int*>(Arr)[-2];
}
*)

Function Hack_High(Ptr: Pointer): UInt32;
type
PUInt32 = ^UInt32;
begin
Result := (PUInt32(Ptr) - sizeof(UInt32))^;
end;

Function Hack_Length(Ptr: Pointer): UInt32;
begin
Result := Hack_High(Ptr) + 1;
end;

Function Hack_ReferenceCount(Ptr: Pointer): Integer;
type
PInt32 = ^Int32;
begin
Result := (PInt32(Ptr) - (sizeof(Int32) * 2))^;
end;

Function IsArrayPointer(Ptr: Pointer): Boolean;
begin
Result := (Hack_ReferenceCount(Ptr) > 0);
end;

var
Arr: TIntegerArray;
I: Integer;
S: String;
begin
SetLength(Arr, 10);
S := 'Hello';
writeln(IsArrayPointer(@I)); //No.. obvious.
writeln(IsArrayPointer(@Arr[0])); //Yes.. obvious.
writeln(IsArrayPointer(@S[1])); //Yes.. strings are char arrays.
end.
Nice! I knew you'd have some kind of hack :)

Doesn't help much towards the generic quicksort function though, since we'd need a "hacked" swap function as well

Dgby714

2014-03-24 01:03:14

From: Zyt3x
Nice! I knew you'd have some kind of hack :)

Doesn't help much towards the generic quicksort function though, since we'd need a "hacked" swap function as well


Write a quicksort method that uses a pointer and some type info (say item size, and count?) then write wrapper for each array type

Brandon

2014-03-24 19:31:14

From: Zyt3x
Nice! I knew you'd have some kind of hack :)

Doesn't help much towards the generic quicksort function though, since we'd need a "hacked" swap function as well


From: Dgby714
Write a quicksort method that uses a pointer and some type info (say item size, and count?) then write wrapper for each array type



This is the best I could come up with in all honesty:


Function PtrLength(Ptr: Pointer): UInt32;
type
PUInt32 = ^UInt32;
begin
Result := (PUInt32(Ptr) - sizeof(UInt32))^ + 1;
end;

Procedure PtrSwap(A, B: Pointer; Size_Ptr: Integer);
type
BytePtr = ^Byte;
var
I: Integer;
T: Byte;
Begin
T := 0;
While (I < Size_Ptr) Do
Begin
T := BytePtr(A[I])^;
BytePtr(A[I])^ := BytePtr(B[I])^;
BytePtr(B[I])^ := T;
Inc(I);
End;
End;

Procedure PtrSort(List: Pointer; Size_Ptr: Integer; Comparator: Function(A, B: Pointer): Boolean);
var
Size: Integer;
I, J: Integer;
Begin
I := 0;
Size := PtrLength(List) * Size_Ptr;

While(I < Size) Do
Begin
J := 0;
While(J < Size - I - Size_Ptr) Do
Begin
If (Comparator(List[J + Size_Ptr], List[J])) Then
PtrSwap(List[J + Size_Ptr], List[J], Size_Ptr);
J := J + Size_Ptr;
End;
I := I + Size_Ptr;
End;
End;

Function IntComparator(A, B: Pointer): Boolean;
Begin
Result := Integer(A^) < Integer(B^);
End;

Function CharComparator(A, B: Pointer): Boolean;
Begin
Result := Char(A^) < Char(B^);
End;

Function FloatComparator(A, B: Pointer): Boolean;
Begin
Result := Extended(A^) < Extended(B^);
End;

Function TPointXSorter(A, B: Pointer): Boolean;
Begin
Result := TPoint(A^).X < TPoint(B^).X;
End;

var
CA: TCharArray;
IA: TIntegerArray;
FA: Array Of Extended;
PA: TPointArray;
begin
CA := ['a', 'j', 'c', 'i', 'h', 'g', 'd', 'e', 'b', 'f'];
IA := [1, 3, 9, 7, 2, 4, 10, 6, 5, 8];
FA := [1.0, 1.3, 1.9, 1.7, 1.2, 1.4, 1.1, 1.6, 1.5, 1.8];
PA := [Point(1, 0), Point(3, 10), Point(9, 11), Point(7, 200), Point(2, 2), Point(4, 1), Point(10, 13), Point(6, 7), Point(5, 3), Point(8, 4)];

PtrSort(@CA[0], SizeOf(CA[0]), @CharComparator);
writeln(CA);

PtrSort(@IA[0], SizeOf(IA[0]), @IntComparator);
writeln(IA);

PtrSort(@FA[0], SizeOf(FA[0]), @FloatComparator);
writeln(FA);

PtrSort(@PA[0], SizeOf(PA[0]), @TPointXSorter);
writeln(PA);
end.


[a, b, c, d, e, f, g, h, i, j]
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9]
[{X = 1, Y = 0}, {X = 2, Y = 2}, {X = 3, Y = 10}, {X = 4, Y = 1}, {X = 5, Y = 3}, {X = 6, Y = 7}, {X = 7, Y = 200}, {X = 8, Y = 4}, {X = 9, Y = 11}, {X = 10, Y = 13}]

With the above hackery, all that is required of the user is to create functions for comparing as follows:


Function IntComparator(A, B: Pointer): Boolean;
Begin
Result := Integer(A^) < Integer(B^);
End;

Function CharComparator(A, B: Pointer): Boolean;
Begin
Result := Char(A^) < Char(B^);
End;

Function FloatComparator(A, B: Pointer): Boolean;
Begin
Result := Extended(A^) < Extended(B^);
End;

Function TPointXSorter(A, B: Pointer): Boolean;
Begin
Result := TPoint(A^).X < TPoint(B^).X;
End;

Function SomethingComparator(A, B: Pointer): Boolean;
Begin
Result := Something(A^) < Something(B^); //Use less-than for ascending order OR use greater-than to sort in descending order..
End;


You pass the comparator function to the generic sort function and voila, it sorts whatever data it is you are trying to sort..

If anyone wants a proper explanation, just let me know and I'll write how it works.. You'll need to understand how swaps truly work (using byte swapping) and a lot of pointer arithmetic..

Not many would care how it works.. Just as long as it does.. And it does.

Gu3st...

2014-03-25 21:19:25

Thanks @Brandon; :) Pointer arithmetic isn't one of my strong suites and I'd never been able to think about that at all.
I'd love it if you could explain it more thoroughly for me

Brandon

2014-03-26 12:30:28

From: Zyt3x
Thanks @Brandon; :) Pointer arithmetic isn't one of my strong suites and I'd never been able to think about that at all.
I'd love it if you could explain it more thoroughly for me



Procedure PtrSwap(A, B: Pointer; Size_Ptr: Integer);
type
BytePtr = ^Byte;
var
I: Integer;
T: Byte;
Begin
T := 0;
While (I < Size_Ptr) Do
Begin
T := BytePtr(A[I])^;
BytePtr(A[I])^ := BytePtr(B[I])^;
BytePtr(B[I])^ := T;
Inc(I);
End;
End;


We know that a byte is size 1 and an integer is 4 bytes. You can check by doing SizeOf(Integer) and SizeOf(Byte).
So to swap integers using bytes, we swap 1 * 4 bytes.

To make it generic, we use a for-loop. From 0 to byte_size and swap every loop. That's what the above does.

For example, in the above we can now do:


PtrSwap(@Some_Integer, @Some_Integer2, 4);
PtrSwap(@Some_Char, @Some_Char, 1);

Now instead of writing 4 (for integers) and 1 (for chars), we use the sizeof operator and we do: Sizeof(Integer); or Sizeof(Char); respectively.


This picture should make it much easier to visualize:


To swap TInegerArray[0] and TIntegerArray[1] in the above, we need to swap 0, 0, 0, 0 (all bytes from 0 to 4) with 1, 0, 0, 0 (all bytes from 4 to 8). That requires us to loop 4 times.

Now for the sorting..


Procedure PtrSort(List: Pointer; Size_Ptr: Integer; Comparator: Function(A, B: Pointer): Boolean);
var
Size: Integer;
I, J: Integer;
Begin
I := 0;
Size := PtrLength(List) * Size_Ptr;

While(I < Size) Do
Begin
J := 0;
While(J < Size - I - Size_Ptr) Do
Begin
If (Comparator(List[J + Size_Ptr], List[J])) Then
PtrSwap(List[J + Size_Ptr], List[J], Size_Ptr);
J := J + Size_Ptr;
End;
I := I + Size_Ptr;
End;
End;

We've already established that in order to swap an integer, we must swap 4 bytes. To swap a char, we swap 1 byte, to swap a double, we swap 8 bytes, and so on and so forth..

In the above, we get the length of our array like usual. To get the "byte-length" we do Length * sizeof(Array[0]). Why Array[0]? Because Array[0] is an element in our array. If our array is TIntegerArray, then Array[0] is an integer. Sizeof(Integer) is 4.

Size_Ptr := 4;

Size := Length(TIntegerArray) * Size_Ptr;


Next we have our C-Style for-loop (Simulated using a while loop).

The important piece here is List[J + Size_Ptr]. We saw in our previous image (the one above) that in bytes, 0 is at offset 0, 1 is at offset 4, 2 is at offset 8, 3 is at offset 12 and 4 is at offset 16.

In this case size_ptr is 4. J + 4 will bring us to the next index.. 0, 4, 12, 16.

Each iteration, we increment J by 4: J := J + Size_Ptr.


This offset is passed to Comparator which "casts/converts" it to the right type as follows:


Function SomethingComparator(A, B: Pointer): Boolean;
Begin
Result := Something(A^) < Something(B^); //Use less-than for ascending order OR use greater-than to sort in descending order..
End;

and returns whether or not the values at the two offsets should be swapped. If it returns true, we swap all the bytes at that offset and loop again.


The above explanation uses integers as an example. It is the same using chars or doubles or anything. Just different offsets/amount of bytes to swap/iterate.

Gu3st...

2014-03-26 22:31:24

You must spread some Reputation around before giving it to Brandon again.
What a shame… :/
Thanks for the explanation, Brandon; getting schooled like this is always a pleasure! :D

One thing that caught my eye that you didn't explain is why passing on a pointer to the first index of an array sorts the whole array: PtrSort(@IA[0], SizeOf(IA[0]), @IntComparator);

Brandon

2014-03-26 23:25:50

From: Zyt3x
What a shame… :/
Thanks for the explanation, Brandon; getting schooled like this is always a pleasure! :D

One thing that caught my eye that you didn't explain is why passing on a pointer to the first index of an array sorts the whole array: PtrSort(@IA[0], SizeOf(IA[0]), @IntComparator);



Arrays are pointers under the hood that point to some contiguous memory block. In other words, there are no gaps between each index/block.

A pointer to any element can be found using arithmetic + the address of the first element as shown below:



Arr[0] is the same as AddressOf(Arr) + (sizeof(element) * 0).
Arr[1] is the same as AddressOf(Arr) + (sizeof(element) * 1).
Arr[2] is the same as AddressOf(Arr) + (sizeof(element) * 2).

So on and so forth.. Where "element" is some type: char, int, float, etc..

This is true for many languages. However, in Pascal, there is one caveat.. Arr[0] is NOT the same as AddressOf(Arr) + (sizeof(Arr[0]) * 0).

Why it was designed like this, I have no clue. It is said that @Arr holds information about the array whereas @Arr[0] is the address of the very first element.

So to get the address of the actual elements, we take the address of Arr[0]. Increment that pointer and it moves to the next element. To get the value at that address, de-reference the pointer:


var
Arr: TIntegerArray;
begin
Arr := [5, 9, 7, 4, 3];
writeln(Integer((@Arr + (sizeof(Integer) * 0))^)); //Does NOT print the first element. Undefined behaviour! Possible access violation..

writeln(Integer((@Arr[0] + (sizeof(Integer) * 0))^)); //Prints the first element.
writeln(Integer((@Arr[0] + (sizeof(Integer) * 1))^)); //Prints the second element.
writeln(Integer((@Arr[0] + (sizeof(Integer) * 2))^)); //Prints the third element.
writeln(Integer((@Arr[0] + (sizeof(Integer) * 3))^)); //Prints the fourth element.
writeln(Integer((@Arr[0] + (sizeof(Integer) * 4))^)); //Prints the fifth element.
end.

OR


var
Arr: TIntegerArray;
begin
Arr := [5, 9, 7, 4, 3];
writeln(Integer((@Arr + (sizeof(Arr[0]) * 0))^)); //Does NOT print the first element. Undefined behaviour! Possible access violation..

writeln(Integer((@Arr[0] + (sizeof(Arr[0]) * 0))^)); //Prints the first element.
writeln(Integer((@Arr[0] + (sizeof(Arr[0]) * 1))^)); //Prints the second element.
writeln(Integer((@Arr[0] + (sizeof(Arr[0]) * 2))^)); //Prints the third element.
writeln(Integer((@Arr[0] + (sizeof(Arr[0]) * 3))^)); //Prints the fourth element.
writeln(Integer((@Arr[0] + (sizeof(Arr[0]) * 4))^)); //Prints the fifth element.
end.

OR


var
Arr: TIntegerArray;
begin
Arr := [5, 9, 7, 4, 3];
writeln(Integer((@Arr + 0)^)); //Does NOT print the first element. Undefined behaviour! Possible access violation..

writeln(Integer((@Arr[0] + 0)^)); //Prints the first element.
writeln(Integer((@Arr[0] + 4)^)); //Prints the second element.
writeln(Integer((@Arr[0] + 8)^)); //Prints the third element.
writeln(Integer((@Arr[0] + 12)^)); //Prints the fourth element.
writeln(Integer((@Arr[0] + 16)^)); //Prints the fifth element.
end.

OR


var
Arr: TIntegerArray;
begin
Arr := [5, 9, 7, 4, 3];
writeln(Integer((@Arr)^)); //Does NOT print the first element. Undefined behaviour! Possible access violation..

writeln(Arr[0]); //Prints the first element.
writeln(Arr[1]); //Prints the second element.
writeln(Arr[2]); //Prints the third element.
writeln(Arr[3]); //Prints the fourth element.
writeln(Arr[4]); //Prints the fifth element.
end.

All are pointer arithmetic. In fact, the [] operator is just sweet syntax sugar that does the above arithmetic for you. :)

Gu3st...

2014-03-26 23:28:02

From: Brandon
Arrays are pointers under the hood that point to some contiguous memory block. In other words, there are no gaps between each index/block.

A pointer to any element can be found using arithmetic + the address of the first element as shown below:



Arr[0] is the same as AddressOf(Arr) + (sizeof(element) * 0).
Arr[1] is the same as AddressOf(Arr) + (sizeof(element) * 1).
Arr[2] is the same as AddressOf(Arr) + (sizeof(element) * 2).

So on and so forth.. Where "element" is some type: char, int, float, etc..

This is true for many languages. However, in Pascal, there is one caveat.. AddressOf(Arr[0]) is NOT the same as AddressOf(Arr) + (sizeof(Arr[0]) * 0).

Why it was designed like this, I have no clue. It is said that @Arr holds information about the array whereas @Arr[0] is the address of the very first element.

So to get the address of the actual elements, we take the address of Arr[0]. Increment that pointer and it moves to the next element.
Well that explains some errors I ran into as well :p

Thanks, man. :)

Gu3st...

2014-04-05 16:19:11

@Brandon; I forgot to post the generic quicksort procedure I created utilizing your functions, but here it is :)
procedure ptrQuickSort(list : Pointer; const sizePtr : Integer;
distArr : TExtendedArray; const sortUp : Boolean;
const lowIndex, highIndex : Integer);
var
iLow := lowIndex;
iHigh := highIndex;
midDist := distArr[(iLow + iHigh) shr 1];
size := ptrLength(list) * sizePtr;
begin
if ptrLength(list) <> length(distArr) then exit();

repeat
if sortUp then
begin
while DistArr[iLow] < midDist do inc(iLow);
while DistArr[iHigh] > midDist do dec(iHigh);
end else
begin
while DistArr[iLow] > midDist do inc(iLow);
while DistArr[iHigh] < midDist do dec(iHigh);
end;

if iLow <= iHigh then
begin
swap(DistArr[iLow], DistArr[iHigh]);
ptrSwap(list[iLow * sizePtr], list[iHigh * sizePtr], sizePtr);

inc(iLow);
dec(iHigh);
end;
until (iLow > iHigh);
if iHigh > lowIndex then ptrQuickSort(list, sizePtr, distArr, sortUp, lowIndex, iHigh);
if iLow < highIndex then ptrQuickSort(list, sizePtr, distArr, sortUp, iLow, highIndex);
end;

procedure ptrQuickSort(list : Pointer; distArr : TExtendedArray; const sortUp : Boolean = True; lowIndex, highIndex : Integer = -1); overload;
begin
if (lowIndex = -1) then lowIndex := 0;
if (highIndex = -1) then highIndex := ptrLength(list) - 1;
ptrQuickSort(list, sizeOf(list[0]), distArr, sortUp, lowIndex, highIndex);
end;

Thanks again ^_^

Testing:
procedure TIntegerArray.quickSort(const distArr : TExtendedArray; const sortUp : Boolean = True; const lowIndex, highIndex : Integer = -1);
begin
ptrQuickSort(@self[0], distArr, sortUp, lowIndex, highIndex);
end;

var
testArr := TIntegerArray([3, 5, 1, 2, 4, 0]);
distArr := TExtendedArray([3.0, 5.0, 1.0, 2.0, 4.0, 0.0]);

begin
testArr.quickSort(distArr);
writeLn(testArr);
end.
Result:
Compiled successfully in 531 ms.
[0, 1, 2, 3, 4, 5]
Successfully executed.

Brandon

2014-04-05 16:25:10

From: Zyt3x

Result:
Compiled successfully in 531 ms.
[0, 1, 2, 3, 4, 5]
Successfully executed.



NICE!
You must spread some Reputation around before giving it to Zyt3x again.
Nice use of recursion too!