PDA

View Full Version : A lot About Lape..



Brandon
07-26-2013, 08:50 AM
Welcome to my first Lape-Tutorial to help everyone since most of the new Simba scripts will be using it anyway..

I don't know that much so far so I'll just help out with what I know and hope that everyone understands it and can help others out.




Default Parameters and Function overloading

There isn't much to say here but here goes. Imagine you wanted to write a function that can have a default parameter. In other words, a parameter that the user does not HAVE to specify but they could if they wanted. You have two options.

The first option is to overload the function as follows so that it has two separate signatures:

Overloads:

Procedure Meh(a, b: Integer);
begin
writeln('Function Meh with 2 parameters a, b. C does not exist so assume 0.');
end;

Procedure Meh(a, b, c: Integer); overload;
begin
writeln('Function Meh with 3 parameters: a, b, c. User must specify C.');
end;


Now if you notice, these two functions have the same name and both return nothing (void). However, they have a different signature. By that, I mean they have different parameters. Thus they are different functions. In order for the Lape interpreter to accept two functions or procedures with the same name, you must add the "overload" keyword at the end of the declaration of the function doing the overloading. It is safe to add overload for both functions but it's a waste of time. The first function is considered the base function and any function after it with the same name but different signature is called an overload function.

To use the above functions, the user can simply do:


begin
Meh(1, 2); //Calls the first function.
Meh(1, 2, 3); //Calls the second function.
end.


Overloading can also be done with different types and parameters:

Procedure Meh(a, b: Integer);
begin
writeln('Function Meh with 2 parameters a, b. C does not exist so assume 0.');
end;

Procedure Meh(a, b, c: Integer); overload;
begin
writeln('Function Meh with 3 parameters: a, b, c. User must specify C.');
end;

Procedure Meh(Area: TBox); overload;
begin
writeln('Function With TBox as parameter instead');
end;

Procedure Meh(a, b: Integer; c: TBox); overload;
begin
writeln('Two int paramters and 1 tbox.');
end;



Default Parameters:
There are times when function overloading is more than enough, but there's no need to have two functions just to emit the last parameter. In such times, we can use something called "Default Parameters". Default parameters are different from function overloading in that you get to specify the parameter being used as default. In the previous example, we saw that the user has to enter a value for parameter C or else it'd be emitted by calling the first function which doesn't have C.
With default parameters, you can have the best of both worlds and still specify what value C should be if the user enters nothing.

Take the following for example:


procedure Foo(a, b: Integer; C: Integer = 3);
begin
writeln(C);
end;


We see that Foo is a procedure that takes 3 parameters. The first two aka "a" and "b" must be entered by the user no matter what. Parameter C is special in that it is a default parameter. It is "optional". The user can either enter a value or just only enter a and b. If the user enters a value, that value is used instead of the value we put for C. If the user enters nothing, it uses the value we entered.

Example:

procedure Foo(a, b: Integer; C: Integer = 3);
begin
writeln(C);
end;

begin
Foo(1, 2); //This will print 3 because 3 is the default value and we only entered a value for a and b.
Foo(1, 2, 9); //This will print 9 because we entered a third value and it "overrides" the default value.
end.


You can also have multiple default parameters:

procedure Foo(a, b: Integer; C: Integer = 2; D: Integer = 3);
begin
writeln(C);
writeln(D);
end;

begin
Foo(1, 2); //Prints 2 and 3.
Foo(1, 2, 4); //Prints 4 and 3.
Foo(1, 2, 4, 9); //Prints 4 and 9. Notice that you cannot enter A, B, D. It always goes in order of A, B, C, D.
end.


Or all parameters optional:

procedure Foo(a, b, c: Integer = 2);
begin
writeln(c);
end;

begin
Foo();
Foo(1);
Foo(1, 2);
Foo(1, 2, 3);
end.


As you can see above. If the user enters nothing, it uses the value the developer/scripter set as default. However, if the user enters a value, it will use the user's value instead. As you can see for yourselves, only one function is created and that the last parameter has priority of being optional over all other parameters before it. In other words, parameters before it has to be entered before you can enter the last one. You cannot skip inputting parameters as shown previously.


Downsides and Abuse:
There comes a time when users abuse and misuse default parameters in combination with function overloads. Take the following for example:


procedure Foo(a, b: Integer; c: Integer = 2);
begin
writeln(c);
end;

procedure Foo(a, b, c: Integer); overload;
begin
writeln(c);
end;

begin
Foo(1, 2); //Calls first Foo.
Foo(1, 2, 3); //ERROR! Which one should it call?
end.


As shown above, the first function always has priority over all others because it is known as the "base" function. Thus the first call to foo calls the first foo function and prints 2. However, the second call to Foo produces an error. But WHY?! Here's the thing. The reason the first call succeeded is because it matched the signature the user specified in the function call.
Thus it never had to look for an overload. However, if it finds two functions with the same name and signature, it will get confused on which one the user is talking about. For example, the First function has 3 parameters with the last being optional. The second function has 3 parameters with none being optional.

However, the first function already covers the second one as well. Both have three of the same type of parameters and both have the same name, thus both are the same function. The error you now get is:

Exception in Script: Don't know which overloaded method to call with params (Int32, Int32, Int32) at line 12, column 3

Be careful when using optional parameters and overloading of a particular function. Other than that, use it to your heart's content.



Type Extension:
Remember in Pascal script when you had to write a function to do a certain task for you. For example, to Insert a string into another string, you'd have to call a function to do that and clog up your scripts? Well type-extension allows you to use a type as though it were a class.

Take for example this pascal script example:

procedure InsertIntoString(var StrToModify: String; StringToInsert: String);
begin
StrToModify := StrToModify + StringToInsert;
end;

var
B: String;
begin
InsertIntoString(B, 'Hello World');
end.



And this Lape Example:

Procedure String.Insert(StrToInsert: String);
Begin
Self := Self + StrToInsert;
End;

var
B: String;
begin
B.Insert('Hello World');
writeln(B);
end.


Notice anything cool? Pretty neat huh? Well you can do this for quite a lot of different types. You can do it for records, existing simba types like string, integer, arrays, etc.. Imagine doing Array.erase(0); and it'll erase the first index in the array.. OR
Array.find(1); and it searches the whole array to find a value and return the index.
Such a thing can be done as follows:


function TIntegerArray.find(ValueToFind: Integer): Integer;
var
I: Integer;
begin
For I := 0 To High(Self) do
if (Self[I] = ValueToFind) then
begin
Result := I;
Exit;
end;
Result := -1;
end;

var
TIA: TIntegerArray;
begin
TIA := [1, 2, 7, 4, 5];
writeln(TIA.Find(7));
end.


As you can see, the above will search the whole TIntegerArray to find the value 7 and then prints the index at which it was found (2). Type extensions introduces object oriented programming into scripting! :D



Variable Initialization:
In most languages, we can initialize variables upon declaration. This way, we never forget what value they start with. Also, we avoid having to depend on the compiler initializing them to some default value.

For example, most users would say that the default value for a pointer is null or nil or 0. This is NOT ALWAYS true. Sometimes, they default value of an integer or pointer is some random value or random location in memory. Thus users always give them a value before use so as to prevent problems later down the line.


In Pascal-Script, we do the following:

var
I: Integer; //Sets default value of 0 in Simba.
Str: String; //Sets default value of '' in Simba.
begin
I := 2; //Assignment within the body of the function. Overwrites the default value. Thus a value has been set twice.
Str := 'Hello World'; //Assignment to Str within the body of the function.
writeln(I);
writeln(Str);
end.


Lape introduces variable initialization as shown in the following example:

var
I: Integer = 2; //Integer.
Str: String = 'Hello World'; //String.
TIA: TIntegerArray = [1, 2, 3, 4]; //Dynamic Array.
TIA2: array[0..4] of integer = [1, 2, 3, 4, 5]; //Constant Array.
begin
writeln(I); //Prints 2.
writeln(Str); //Prints Hello World.
writeln(TIA); //Prints 1, 2, 3, 4.
writeln(TIA2); //Prints 1, 2, 3, 4, 5.
end.


As you can see above, the pascal version is longer and does assignment twice. However, the Lape version does an assignment to I only once (at least I think so due to this being the nature of C++ class initialization). I also have a firm belief that it only assigns once since it assigns at compile time. However, there is no need to do an assignment within the body of the function and the variable can be used immediately.

There is one thing to note about variable initialization. Only constant expressions are allowed. For example, you cannot do:

var
I: Integer = BitmapFromString(512, 512, '');
begin
writeln(I);
end.


It simply will not compile because BitmapFromString is a function call and variable initialization is done at compile time whereas BitmapFromString is a run-time function. Lape also does not evaluate or have constexpr functions as C++ does and thus this is not possible.



Records and Inheritance:
Imagine you have an object in the real world such as a "Vehicle". Now we all know there can be many types of vehicles. Examples: Cars, Trucks, Motorcycles, Bikes, etc.. They are all a form of transportation and they all share some characteristics in common.
For example, all of the above listed vehicles may shared "wheels" because they all have wheels and they all have a "name". They may or may not have "doors".

These things listed in quotes are all known as properties. Wheels, Names, Doors are all properties of a vehicle.
To represent such a hierarchy in any programming language, we introduce something known as inheritance. All sub-classes known better as "children" share the same properties as their parent category "vehicle".

Such a hierarchy may be represented as follows:


type Vehicle = record
Name: String;
Wheels: Integer;
Doors: Integer;
Seats: Integer;
end;

type Car = record(Vehicle)
Automatic: Boolean;
MaxSpeed: Integer;
end;

type Truck = record(Vehicle)
OffRoad: Boolean;
FourWheelDrive: Boolean;
MaxSpeed: Integer;
end;

var
V: Vehicle = ['Vehicle', 4, 2, 4];
C: Car = ['Car', 4, 2, 4, true, 700];
T: Truck = ['Truck', 4, 2, 4, true, true, 400];
begin
writeln(V);
writeln(C);
writeln(T);
end;


When you print the above, it prints:

{NAME = Vehicle, WHEELS = 4, DOORS = 2, SEATS = 4}
{NAME = Car, WHEELS = 4, DOORS = 2, SEATS = 4, AUTOMATIC = True, MAXSPEED = 700}
{NAME = Truck, WHEELS = 4, DOORS = 2, SEATS = 4, OFFROAD = True, FOURWHEELDRIVE = True, MAXSPEED = 400}


As you can see, Vehicle is the parent class. It is at the top of the hierarchy. The Car and Truck as "Types Of" vehicles.

Relationships:
To be more clear, we call these relationships. Vehicle is the parent and Car and Truck are the children. Brother and Sister where the Vehicle is Mother.
In programming terms, we call this relationship the "Is-A" relationship.
Take the following for example:


type Vehicle = record
Name: String; //Vehicle "Has-A" name.
Wheels: Integer; //Vehicle "Has" wheels.
Doors: Integer; //Vehicle "Has" doors.
Seats: Integer; //Vehicle "Has" Seats.
end;

type Car = record(Vehicle) //Car "Is-A" vehicle. Thus a car has everything vehicle has + more.
Automatic: Boolean; //Car "Has" automatic or manual mode.
MaxSpeed: Integer; //Car "Has" max speed.
end;

type Truck = record(Vehicle) //Truck "Is-A" vehicle. Thus a truck has everything a vehicle has + more.
OffRoad: Boolean; //Truck "Has" offroad type.
FourWheelDrive: Boolean; //Truck "Has" four-wheel drive.
MaxSpeed: Integer; //Truck "Has" Max Speed.
end;

var
V: Vehicle = ['Vehicle', 4, 2, 4]; //Initialize Vehicle if we wanted to.
C: Car = ['Car', 4, 2, 4, true, 700]; //When initializing Car, we must initialize all its parent properties too.
T: Truck = ['Truck', 4, 2, 4, true, true, 400]; //Initializing truck is the same as initializing car. All parent properties too.
begin
writeln(V);
writeln(C);
writeln(T);
end;



As you can see, a truck and a car "Is-A" vehicle but they both "Has/Has-A" their properties.
A truck and a car "Is" both vehicle but a car is NOT a truck. They only share their parent properties!
Those are the only two types of relationships when it comes to inheritance. As explained in the comments, since a car has everything a vehicle has, you must thus initialize all the parent properties of the car structure when declaring it.

It can also be written like:

var
V: Vehicle;
C: Car;
T: Truck;
begin
//Assign to each 'member' of the vehicle structure. Vehicle structure is know as the 'Base' class.
V.Name := 'Vehicle';
V.Doors := 4;
V.Seats := 2;
V.Wheels := 4;

//Assign to each 'member' of the car structure. Car structure is known as the 'Derived' class.
C.Name := 'Car'; //member is inherited from the vehicle parent structure.
C.Doors := 2; //member is inherited from the vehicle parent structure.
C.Seats := 4; //member is inherited from the vehicle parent structure.
C.Wheels := 4; //member is inherited from the vehicle parent structure.
C.Automatic := true;
C.MaxSpeed := 700;

//Assign to each 'member' of the truck structure using short-hand assignment. Truck structure is known as the 'Derived' class.
T := ['Truck', 4, 2, 4, true, true, 400];

writeln(V);
writeln(C);
writeln(T);
end;




Pointers, Addresses, References, Bytes:

References:
How many of you remember using the "var" keyword in Pascal-Script to modify a variable passed to a function?
Under the hood, the var keyword really says "Pass-By-Reference" this variable such that the function can modify it.

Example:

Procedure ModifyInt(var I: Integer); //Pass I by reference..
begin
I := 10; //Modifies the value of I so that I is now 10. Anything that happens to I, affects the parameter I.
end;

Procedure FailModifyInt(I: Integer); //Pass I by value..
Begin
I := 15; //Only modifies a copy of I. Thus changes to the parameter I never happen outside this function.
End;

var
I: Integer = 5;
begin
writeln(I); //prints 5.
ModifyInt(I); //Changes value of I.
writeln(I); //Prints 10.
FailModifyInt(I); //Failure..
writeln(I); //Still Prints 10.
end.


As you can see, I was passed by reference to the first function and thus it was modified. However, the second function passes it by value and only modifies a copy of I. So how does this var keyword really operate.

Var is a keyword that says, pass the address of this variable to the function such that any modifications the function does, affects the data at that address. Since variable I is at that address, any changes at that address, changes the value of I.
Here's the thing though, a REFERENCE can NEVER be NULL. In other words, a reference must be a variable!

Thus the following is invalid code:

Procedure ModifyInt(var I: Integer); //Pass I by reference..
begin
I := 10; //Modifies the value of I so that I is now 10. Anything that happens to I, affects the parameter I.
end;

var
I: Integer = 5;
begin
ModifyInt(0); //ERROR! 0 is not a variable! The address of "0" does not exist.
ModifyInt(I); //Give the function the address of variable I. Aka the location of variable I in memory.
end.


Now we know that var is used to declare variables and to pass variables by reference to a function, let us check out the next operator.



Address-Of:
The Address-Of operator aka the @ sign, gets the address of a variable or member of a structure or even the address of a function. So far, in pascal-script, we've used it to only get the address of functions so that we can a function to another function such as shown below:


Procedure Caller(Callee: Procedure(Number: Integer));
begin
Callee(416); //Call the Callee function.
end;

Procedure CallMeMaybe(Number: Integer);
Begin
writeln('Calling Carlie Rae-Jepsen: ' + ToStr(Number));
End;

begin
Caller(@CallMeMaybe); //Pass the address of "CallMeMaybe" to "Caller"
end.


However, this is not the only use of the address-of operator (@). You can also grab the address of a variable as shown below:

var
I: Integer;
begin
I := 1502525235;
writeln(@I); //Prints: "I"::0x7D58700 (1502525235)
end.


As you can see above, "I" was located at the memory address represented in hexadecimal-->(0x7D58700). The value at that address is the value of I-->(1502525235). Thus, if we modify what is at that address, we will change the value of I. To do this, read on to the next section.



Pointers to Types and Dereferencing:
To create a pointer to a variable, we first need to get the address of the variable. We then assign the address of that variable to some pointer. What is a pointer you ask? A pointer is a variable which holds the address/memory location of another variable, structure or type. Do you see the irony there? Since a pointer is a variable as well, we can have pointers to pointers and pointers to pointers to pointers and so on..

However, we'll leave that for another time and move on. To declare a pointer to a type, we use the following syntax:

type PInt = ^Integer; //we declare a type call PInt. It is a pointer to an integer.

var
I, J: Integer;
Ptr: PInt; //A variable called Ptr which is a pointer to an int.
Ptr2: ^Integer; //A variable called Ptr2 which is a pointer to an int as well.
begin
Ptr := @I; //Ptr now holds the address of I.
Ptr2 := @J; //Ptr2 now holds the address of J.
writeln(Ptr); //prints: "I"::0x7F601C0 (0).
writeln(@I); //prints the same as above :D

writeln(Ptr2); //prints: "J"::0x7F601E0 (0).
writeln(@J); //prints the same as above.
end.


As you can see above, the pointers can only hold the address of some variable, function, or structure. We use the address-of operator to get such information. Now you may notice that we have used the ^ operator followed by some data-type to declare a pointer of such type.

Example:

^Byte; //Pointer to a byte.
^Integer; //Pointer to an int.
^Single; //Pointer to a floating point type. Aka same as ^Extended.
^Vehicle; //Pointer to a vehicle structure.
^Car; //Pointer to a car structure.


So on and so forth.
Now how do we get the value of what is being pointed at? In other words, how can we get the value stored at the address the pointer is holding? Well, the weird thing is.. It's the same operator. Take a look at the following:


var
I: Integer = 150;
J: Integer = 200;
Ptr: ^Integer; //^ followed by Type. Thus pointer to integer.
begin
Ptr := @I; //Ptr now holds the address of I.
J := Ptr^; //Pointer followed by ^.

writeln(J); //Prints the value of I. Prints 150 instead of 250.
end.


Notices that to get the value the pointer points at, we do "Pointer^" and to declare a pointer we do "^Pointer". Yes. I know it looks similar but keep this in mind all the time! "Pointer^" means to dereference and get the value of it. Whereas "^Pointer" means to declare a pointer of some type.
Here is a little exercise. Try and figure out how the following works without running it. Then run it and see if it you got it right.

Test:

type
PInt = ^Integer;
PPInt = ^PInt;

var
I, J: Integer;
Ptr: PInt;
Ptr2: PPint;

begin
I := 700;
J := 5000;

Ptr := @I;
Ptr^ := 300;
Ptr := @J;
Ptr2 := @Ptr;
Ptr2^^ := 100;

writeln(I); //What is the value of I?
writeln(J); //What is the value of J?
end;




Pointers, Arrays, Bytes:
Now if you are comfortable with pointers and modifying variables using them, it is time to learn how a pointer and an array work together to achieve specific tasks such as modifying an array or swapping the values of an array, etc..

The first task is to understand how pointers operate and their different variable sizes. A pointer on 32-bit operating systems are always 4 bytes in size and on 64-bit systems, they are 8-bytes in size. A byte is the smallest usable amount of memory that a computer has.

4 bits = 1 byte.
1 byte = 1 char.
2 bytes = 1 word/short-int.
4 bytes = 1 integer/double-word/dword.
8 bytes = 1 int64/long-int/qword.

Yes that's right. Every integer is 4-bytes long and every character is 1-byte.

Now why should we know this? Well, when doing pointer arithmetic such as iterating a pointer (moving it to the next memory address), we need to know how much to increase it to get to the next desirable cell.

For example, see the following:

var
Ptr: ^Char;
I: Integer;
TIA: array[0..4] of integer;
begin
TIA := [1, 2, 3, 4, 5];
Ptr := @TIA;

For I := 0 To High(TIA) do
begin
writeln(Ptr^); //What do you think it prints?
Inc(Ptr);
end;
end;


See we just assigned a pointer of "char" type to an array of integers. An integer is 4 bytes in size but a char is 1. Thus we are only printing the first byte in each integer. The above will print some random garbage to your screen. It isn't random at all because I know how the assembly registers works but for now, lets just leave it as "random garbage". In order to print all the values in the array, we must use a 4-byte pointer or increase the pointer by 4 every loop.

So lets try creating a pointer to an int then as follows:

type PInt = ^Integer;

var
Ptr: PInt;
I, J: Integer;
TIA: array[0..4] of integer;
begin
TIA := [1, 2, 3, 4, 5];
Ptr := @TIA;

For I := 0 To High(TIA) do
begin
writeln(Ptr^); //What do you think it prints?
Inc(Ptr);
end;
end;


Now what does it print? It of course, prints the values of the array. But why? Well the reason it now prints the value of the array is because Inc(Ptr) increases the Pointer by 1. Since the pointer is an int pointer, it has really increased by 4 bytes.

Thus the above is the same as using a char pointer like so:


type PInt = ^Integer;

var
Ptr: PChar;
I, J: Integer;
TIA: array[0..4] of integer;
begin
TIA := [1, 2, 3, 4, 5];
Ptr := @TIA;

For I := 0 To High(TIA) do
begin
writeln(PInt(Ptr)^); //Cast our Char Pointer to an Int Pointer and dereference it. Aka get the value it points at.
Ptr := Ptr + 4; //Increase the char pointer by 4 bytes.
end;
end;




Size-Of with Pointers; RawPointers:

Now let us say that you aren't sure what architecture the user is using.. In other words, you're not sure whether they are using x32 or x64 bit operating systems. Then how do you know whether to increase the pointer by 4 bytes or by 8? Well, introducing the sizeof operator/macro:


type PInt = ^Integer;

var
Ptr: PChar;
I, J: Integer;
TIA: array[0..4] of integer;
begin
TIA := [1, 2, 3, 4, 5];
Ptr := @TIA; //Can point to an integer-array.

For I := 0 To High(TIA) do
begin
writeln(PInt(Ptr)^);
Ptr := Ptr + sizeof(Integer); //Increase the pointer by the sizeof(integer) in bytes.. An Integer is 4 bytes so sizeof(Integer) is 4.
end;
end;


As you can see above, using sizeof(DataType) is much safer to figure out how much to increase the pointer by. The above will still print all the values in the array and at the same time introduces pointer-safety. If you ever forget what the size of any data-type is, you can do:


writeln(sizeof(DataTypeHere)); //prints the size of a data-type in bytes.


Now what is a raw-pointer? A raw pointer is a pointer that has no size or type. Wtf? How? Well, in C++, this is known as a void pointer. It is not 4 byte, not 2 bytes, not 8, nothing.. To get the value of a raw pointer, you need to cast it to a pointer with a specific type. To declare a raw-pointer, we do the following:


var
I: Integer;
Ptr: Pointer; //Declares a raw pointer ;)
TIA: array[0..5] of integer;
TCA: array[0..5] of char;
begin
TIA := [1, 2, 3, 4, 5, 6];
TCA := ['a', 'b', 'c', 'd', 'e', 'f'];
Ptr := @TIA;

For I := 0 To High(TIA) do
begin
writeln(Integer(Ptr^)); //Cast our raw-pointer to an integer pointer and get the value at that address and print it.
Ptr := Ptr + sizeof(Integer); //we are iterating an integer array. So we increase our raw pointer by sizeof(Integer);
end;


Ptr := @TCA; //Notice that the raw-pointer can point to any data-type.

For I := 0 To High(TCA) do
begin
writeln(char(Ptr^)); //Cast our raw-pointer to an char pointer and get the value at that address and print it.
Ptr := Ptr + sizeof(char); //we are iterating a char array. So we increase our raw pointer by sizeof(char);
end;
end;




Pointers to structures and arithmetic:
Pointers to structures utilize the sizeof operator/macro and pointer-data size quite a lot. See the following on how to print the entire struct below:

type Struct = record
X: Integer;
Y: Word;
Z: Integer;
end;


var
Ptr: Pointer;
S: Struct = [100, 500, 255];
begin
Ptr := @S;

writeln(Integer(Ptr^)); //Print S.X;
Ptr := Ptr + sizeof(Integer); //Move onto the next data member.

writeln(Word(Ptr^)); //Print S.Y;
Ptr := Ptr + sizeof(Word); //Move onto the next data member.

writeln(Integer(Ptr^)); //Print S.Z;
Ptr := Ptr + sizeof(Integer); //Not needed because we already printed the last member.
end;


Notice that we have declared a pointer to the Struct. The pointer holds the address of S. To print the values of S, we move the pointer to the memory location of each data member and then print it. But this is quite tedious isn't it?

Well here is another pointer arithmetic trick. Not only can we add pointers and sizes, we can also subtract, multiply, and divide them. We can also use member offsets. For example:

Let us assume that S is located at 0. If that is true, then S.X is located at 0 bytes from S's address. S.Y is located at 4 from S's address and S.Z is located at 6 bytes from S's address.


type Struct = record
X: Integer; //0 bytes away from Struct.
Y: Word; //4 bytes away from Struct; aka sizeof(X).
Z: Integer; //6 bytes away from Struct; aka sizeof(X) + sizeof(Y).
end;


var
Ptr: Pointer;
S: Struct = [100, 500, 255];
begin
Ptr := @S;

writeln(Integer(Ptr^)); //Prints S.X
writeln(Word((Ptr + 4)^)); //Prints S.Y
writeln(Integer((Ptr + 6)^)); //Prints S.Z
end;



Another example of pointer arithmetic is the following:

type Struct = record
X: Integer;
Y: Word;
Z: Integer;
end;


var
Ptr: Pointer;
SA: array[0..3] of Struct;
I: Integer;
begin
SA[0] := [100, 200, 300];
SA[1] := [400, 500, 600];
SA[2] := [700, 800, 900];
SA[3] := [1000, 1100, 1200];

Ptr := @SA;

For I := 0 To High(SA) do
begin
writeln('SA[' + ToStr(I) + ']: ');
writeln(Integer(Ptr^));
writeln(Word((Ptr + 4)^));
writeln(Integer((Ptr + 6)^));
writeln('');
Ptr := Ptr + sizeof(Struct); //increment by the sizeof(Struct) aka.. sizeof(Integer) + sizeof(Word) + sizeof(Integer).. aka 10.
end;
end;


Now lets say you wanted to print only every 3rd value in the following, you'd do:

var
TIA: Array[0..23] of integer;
I: Integer;
Ptr: Pointer;
begin
TIA := [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24];
Ptr := @TIA;

while (Ptr < @TIA[23]) do
begin
writeln(Integer(Ptr^));
Ptr := Ptr + sizeof(Integer) * 3;
end;
end;




Simulating Polymorphism:
Polymorphism is the concept of having values of different types use a single data-type/interface for handling. Confused? Well remember the vehicle, car, truck example previously shown? If we wanted to create an array of all types of vehicles, how could we do such a thing?

The following code will give you errors:


type Vehicle = record
Name: String;
Wheels: Integer;
Doors: Integer;
Seats: Integer;
end;

type Car = record(Vehicle)
Automatic: Boolean;
MaxSpeed: Integer;
end;

type Truck = record(Vehicle)
OffRoad: Boolean;
FourWheelDrive: Boolean;
MaxSpeed: Integer;
end;

type
PVehicle = ^Vehicle;

var
V: Vehicle = ['Vehicle', 4, 2, 4];
C: Car = ['Car', 4, 2, 4, true, 700];
T: Truck = ['Truck', 4, 2, 4, true, true, 400];

VA: Array[0..2] of Vehicle;
begin
VA[0] := C; //Even though a car is a vehicle, we cannot assign it to a vehicle array. This would introduce a problem known as "slicing". Lape stops this by not allowing you to do it.
VA[1] := T; //Same thing here.
end;


So how can we create an array that can hold ALL types of vehicles? The answer is polymorphism through pointers. Since Car and Truck are both "Vehicles" and are children of the vehicle structure, we can create a pointer to both a car and a truck and store it in an array of vehicle:

The following code is 100% valid and polymorphic (closest to at least):

type Vehicle = record
Name: String;
Wheels: Integer;
Doors: Integer;
Seats: Integer;
end;

type Car = record(Vehicle)
Automatic: Boolean;
MaxSpeed: Integer;
end;

type Truck = record(Vehicle)
OffRoad: Boolean;
FourWheelDrive: Boolean;
MaxSpeed: Integer;
end;

type
PVehicle = ^Vehicle;

var
V: Vehicle = ['Vehicle', 4, 2, 4];
C: Car = ['Car', 4, 2, 4, true, 700];
T: Truck = ['Truck', 4, 2, 4, true, true, 400];

VA: Array[0..2] of PVehicle;
PV, PV2: PVehicle;
begin
VA[0] := @C;
VA[1] := @T;
PV := @C;
PV2 := @T;

writeln(PV^);
writeln(PV2^);
writeln(VA[0]^);
writeln(VA[1]^);
end;


As you can see, we just created an array of Pointers to Vehicle type. Since all pointers are the same size (4 bytes on x32 and 8 bytes on x64), we can simply store the address of each vehicle type into any pointer. This is known as a polymorphic array. An array of parent holding pointers to children. You can also see that a pointer to the vehicle type can hold the address of a child type. Thus we can now print out that array and it'll print all the children. Useful for holding an array of "anything" or array of children basically. This can be done with all data-types. Polymorphism has many uses and is a very powerful tool! Unfortunately Lape does not have "typeof" to tell which type a pointer is.. But that is ok we can still use polymorphism simulation and many pointer features.

Cool huh?

Kasi
07-26-2013, 11:48 AM
So sexy, will read when I get home, was actually in dire need of a lape tutorial

Rich
07-26-2013, 12:21 PM
Love it. Awesome tut Brandon, very well written and very informative. More rep for you.

rj
07-26-2013, 02:22 PM
Thanks, but a question about the records, so your saying I could do like a record like a user can add as many custom objects as they want like:



customObject[0].color
customObject[0].hue
customObject[0].sat
customObject[0].tol
customObject[0].width
customObject[0].Height

customObject[1].color
customObject[1].hue
customObject[1].sat
customObject[1].tol
customObject[1].width
customObject[1].Height

Brandon
07-26-2013, 04:37 PM
Thanks, but a question about the records, so your saying I could do like a record like a user can add as many custom objects as they want like:



customObject[0].color
customObject[0].hue
customObject[0].sat
customObject[0].tol
customObject[0].width
customObject[0].Height

customObject[1].color
customObject[1].hue
customObject[1].sat
customObject[1].tol
customObject[1].width
customObject[1].Height



Yes. The above code you have there is an array of records

euphemism
07-26-2013, 04:56 PM
Thanks, but a question about the records, so your saying I could do like a record like a user can add as many custom objects as they want like:
-snip-

You could do this with PascalScript as well, i.e. this isn't a feature brought around by Lape.

loragnor
07-26-2013, 07:19 PM
In truth Brandon, one of the hardest things I find to deal with in PascalScript is the lack of these features that are usable in nearly every other scripting and programming language I know. So, my question is: How do I write a script using Lape rather than PS? Is there an example of how to set one up?

Le Jingle
07-26-2013, 07:34 PM
First off, thank you very much for the tutorial on some Lape features; very valuable for those entering the lape side of scripts. :)

Also, I'm pleased to better understand the syntax as you've illustrated above too! For instance, I didn't know about the records inheritance feature (i.e. using parenthesis to inherit from a parent type, as well as declaring a variable and initializing it with data members in the order they appear [ex. str, int, etc.]) until after reading this tutorial.

A follow up question I have is: Are records able to be declared in a local function or must that be kept to the global level?

As always, I am very thankful to your contributions Brandon, and always seem to learn new things from you! :D

Cheers as Always,
Le Jingle!

Brandon
07-26-2013, 08:10 PM
In truth Brandon, one of the hardest things I find to deal with in PascalScript is the lack of these features that are usable in nearly every other scripting and programming language I know. So, my question is: How do I write a script using Lape rather than PS? Is there an example of how to set one up?


Open Simba, open the "script" menu at the top and choose interpreter. Select Lape. Simba 1.0 might be needed for some of these features.




First off, thank you very much for the tutorial on some Lape features; very valuable for those entering the lape side of scripts. :)

Also, I'm pleased to better understand the syntax as you've illustrated above too! For instance, I didn't know about the records inheritance feature (i.e. using parenthesis to inherit from a parent type, as well as declaring a variable and initializing it with data members in the order they appear [ex. str, int, etc.]) until after reading this tutorial.

A follow up question I have is: Are records able to be declared in a local function or must that be kept to the global level?

As always, I am very thankful to your contributions Brandon, and always seem to learn new things from you! :D

Cheers as Always,
Le Jingle!


There are limits to it such as global scope of records:

type G = record //valid.
M: Integer;
end;

procedure Foo;
begin
type F = record //invalid.
M: Integer;
end;
end;

Janilabo
07-26-2013, 11:29 PM
Brandon, sir, you are awesome.
Really useful tutorial - especially for the future of Simba! :)

Nice work, I really wish I could hand over some reputation points to you now...

-Jani

nielsie95
07-27-2013, 12:37 PM
Looking good!

A few things:


var
I: Integer := BitmapFromString(512, 512, ''); //use := instead of = for delayed initialization
begin
writeln(I);
end.

Use := for delayed initialization. The variable will now be initialized at the start of the script.


procedure Foo(a, b, c: Integer = 2);
begin
writeln(c);
end;

begin
Foo();
Foo(1);
Foo(1, 2);
Foo(1, 2, 3);
Foo(1, , 3); //Empty second parameter
end.

Leave a parameter empty if you want to use the default value. This also works for record initialization.


type PInt = ^Integer;

var
Ptr: PInt;
I, J: Integer;
TIA: array[0..4] of integer;
begin
TIA := [1..5]; //Fancy array initialization
Ptr := @TIA;

//Access pointer as array
For I := 0 To High(TIA) do
writeln(Ptr[i]^);

//No need for i if you do not use it
For 0 To High(TIA) do
begin
writeln(Ptr^);
Inc(Ptr);
end;
end;

Pointers can be accessed as arrays too and for-loops do not need a variable.

Very useful though, I hope this helps a lot of people! :)

Brandon
07-27-2013, 02:52 PM
Looking good!

A few things:


var
I: Integer := BitmapFromString(512, 512, ''); //use := instead of = for delayed initialization
begin
writeln(I);
end.

Use := for delayed initialization. The variable will now be initialized at the start of the script.


procedure Foo(a, b, c: Integer = 2);
begin
writeln(c);
end;

begin
Foo();
Foo(1);
Foo(1, 2);
Foo(1, 2, 3);
Foo(1, , 3); //Empty second parameter
end.

Leave a parameter empty if you want to use the default value. This also works for record initialization.


type PInt = ^Integer;

var
Ptr: PInt;
I, J: Integer;
TIA: array[0..4] of integer;
begin
TIA := [1..5]; //Fancy array initialization
Ptr := @TIA;

//Access pointer as array
For I := 0 To High(TIA) do
writeln(Ptr[i]^);

//No need for i if you do not use it
For 0 To High(TIA) do
begin
writeln(Ptr^);
Inc(Ptr);
end;
end;

Pointers can be accessed as arrays too and for-loops do not need a variable.

Very useful though, I hope this helps a lot of people! :)

Ahh! Ill add these to the op when I get home. I knew about the for loop not needing a variable but I thought that was a glitch :S so I left it out lol.
Didn't know you could skip parameters either so that it uses the default or that it had delayed initialization. Ill add these with explanations.

Thanx!

Flight
07-28-2013, 01:31 AM
This is a really great tutorial and essential for the upcoming release of SRL-6. I'm still very unfamiliar with Lape so when I need to start getting the feel of it this is the first place I'll be looking for answers. Thanks a million Brandon! :spongebob:

DemiseScythe
07-28-2013, 01:38 AM
Why doesn't StrToInt work on Lape?

Exception in Script: Expected variable of type "AnsiString", got "record [0]array of AnsiString; [4]array of record [0]Int32; [4]Int32; [8]Int32; [12]Int32; end; [8]array of Int32; end" at line 120, column 28


Writeln(S[0]);
Writeln(SS[0]);
Result := Round((StrToInt(S[0])/StrToInt(SS[0]))*100);

This is S[0] and SS[0]:

Writeln(S[0]) => {TEXT = [4191], AREAS = [{X1 = 628, Y1 = 605, X2 = 652, Y2 = 613}], COLOURS = [16777215]}
Writeln(SS[0]) => {TEXT = [6765], AREAS = [{X1 = 657, Y1 = 605, X2 = 683, Y2 = 613}], COLOURS = [16777215]}

Technically I want to know how to divide '4191' by '6765' in Lape.

I think I need to just include the text, something like S[0].TEXT instead

Result := Round((StrToInt(S[0].Text)/StrToInt(SS[0].Text))*100);

It still doesn't work.

Result := Round((StrToInt(S[0].Text) div StrToInt(SS[0].Text))*100);

It doesn't work either.

Result := Round((StrToInt('4881') div StrToInt('6765'))*100);

That works but the result is 0...

Brandon
07-28-2013, 01:52 AM
Why doesn't StrToInt work on Lape?

Exception in Script: Expected variable of type "AnsiString", got "record [0]array of AnsiString; [4]array of record [0]Int32; [4]Int32; [8]Int32; [12]Int32; end; [8]array of Int32; end" at line 120, column 28


You cannot do that :l
StrToInt only accepts strings and converts them to integers.

You'd have to do:


StrToInt(S[0].Text[0]) / StrToInt(SS[0].Text[0]) //or w/e..

Dgby714
07-28-2013, 02:30 AM
Just a note, records aren't restricted to global declarations, methods can have type, constant, variable and even other methods declared.

function a(): string;
function b(): string;
begin
Result := '2';
end;
type
TTest = record
d: string;
end;
const
c = '3';
var
Test: TTest = ['4'];
begin
Result := '1' + b() + c + Test.d;
end;

begin
WriteLn(a()); //1234
end.

stata
08-29-2013, 08:20 PM
most complete guide on Lape I've seen!

One question though; is it not possible to dereference pointers to types? If so, that would be a much easier way of pulling out the values in an instance of a type from its pointer.

Brandon
08-29-2013, 09:34 PM
most complete guide on Lape I've seen!

One question though; is it not possible to dereference pointers to types? If so, that would be a much easier way of pulling out the values in an instance of a type from its pointer.



I'm not sure what you mean but the OP contains a lot of examples on dereferencing.. One specific example that contains a comment about de-referencing is:


type PInt = ^Integer;

var
Ptr: PChar;
I, J: Integer;
TIA: array[0..4] of integer;
begin
TIA := [1, 2, 3, 4, 5];
Ptr := @TIA;

For I := 0 To High(TIA) do
begin
writeln(PInt(Ptr)^); //Cast our Char Pointer to an Int Pointer and dereference it. Aka get the value it points at.
Ptr := Ptr + 4; //Increase the char pointer by 4 bytes.
end;
end;

stata
08-29-2013, 11:26 PM
derefrencing type pointers, as in you have a type, and the pointer is a pointer of that type. Isn't the example you've given just aliasing the integer pointers? I don't get the point of that.

although now that I've given the OP another look, I do see that you've derefrenced a Vehicle pointer

is there a shortcut to derefrencing a type pointer and then getting one of its elements like so


type MyType = record
element1: Integer;
element2: Short;
end;

var
MyTypePointer: ^MyType;

begin
writeln((MyTypePointer^).element1);
end

like in C, this would be MyTypePointer->element1

Brandon
08-30-2013, 12:06 AM
The point of the example was to show the difference in bytes between the two..

If a char pointer points to an int type, then it only points to 1 byte out of the 4. However, casting the char pointer to an int pointer allows it to point to the int itself and not the individual bytes of that int. The example was simply showing that an int is 4 bytes (aka chars) and the right pointer should be used to retrieve values unless you want to do the math manually. It's easier to just cast or alias the pointer to the type pointed to.


type foo = record
I: Integer;
end;

var
Something: Foo;
Ptr: ^Foo;
begin
Something.I := 10;
Ptr := @Something //Put a semi-colon here.. The simba tags for this forum is broken and if I put it here, it instead does a "Mention".
writeln(Ptr^.I); //same as (Ptr^).I;
end.


I believe you are trying to do this? Which I thought was on the OP. Guess not =(

stata
08-30-2013, 12:14 AM
Yup, that's what I wanted (and pretty much what I wrote except w/o parentheses lol)

Alright, that clarifies things a lot man.

One more question. If I pass an array to a procedure like so...


procedure bob(a: TPointArray);
begin
nonsense;
end;

var
MyTPA: TPointArray;

begin
bob(MyTPA);
end;


So here, am I sending bob the pointer to the TPA, or is bob gonna create a copy of my TPA and mess with the copy?
Also, is this the case for other kinds of arrays (constant length, etc)?

Also, if I was passing a string (which is not exactly a char array?), am I sending bob the pointer to the string?

Basically, is pass by reference the default for arrays/strings like it is in C and I think Java

Zyt3x
09-01-2013, 11:46 AM
Yup, that's what I wanted (and pretty much what I wrote except w/o parentheses lol)

Alright, that clarifies things a lot man.

One more question. If I pass an array to a procedure like so...


procedure bob(a: TPointArray);
begin
nonsense;
end;

var
MyTPA: TPointArray;

begin
bob(MyTPA);
end;


So here, am I sending bob the pointer to the TPA, or is bob gonna create a copy of my TPA and mess with the copy?
Also, is this the case for other kinds of arrays (constant length, etc)?

Also, if I was passing a string (which is not exactly a char array?), am I sending bob the pointer to the string?

Basically, is pass by reference the default for arrays/strings like it is in C and I think Java
There are multiple methods of declaration:
procedure bob(a: TPointArray); // Call-by-value (default)
procedure bob(var a: TPointArray); // Call-by-reference
procedure bob(const a: TPointArray); // Bob has a pointer to the original variable

There are also other methods like "in", "out" and others, but I think you'll be just fine with the above three for now

stata
09-01-2013, 06:05 PM
There are multiple methods of declaration:
procedure bob(a: TPointArray); // Call-by-value (default)
procedure bob(var a: TPointArray); // Call-by-reference
procedure bob(const a: TPointArray); // Bob has a pointer to the original variable

There are also other methods like "in", "out" and others, but I think you'll be just fine with the above three for now

obviously there are multiple methods of declaration... that's why I'm asking

that last declaration confuses me. Is that the same as this
procedure bob(a: ^TPointArray);

seems like an odd way to send a pointer, particularly since it seems to imply that "a" cannot be changed by the procedure

and do "in" and "out" work as they did in PS? I'm aware of those...

Brandon
09-01-2013, 06:15 PM
that last declaration confuses me. Is that the same as this
procedure bob(a: ^TPointArray);

seems like an odd way to send a pointer, particularly since it seems to imply that "a" cannot be changed by the procedure

and do "in" and "out" work as they did in PS? I'm aware of those...


Uhh I don't think that const is a pointer. In fact, in C and many C-style languages, a const parameter is useless because either way the function will make a copy of the parameter passed and work on that.

Basically the equivalent of doing:


void func(const int I)
{
//work with that copy of I here..
}

void func(int I)
{
//work with the copy of I here..
}


Either way, it is still a copy with or without the const. So his first and last declarations seem to be the same to me :S Your example:


Procedure Foo(a: ^TIntegerArray);
begin
//work with pointer "a" here..
end;

var
a: TIntegerArray;
begin
a := [1, 2];
Foo(@a);
end.


seems to be right afaik. Unless lape defined the usage of the keyword const wrong or added such a feature, I think that the third declaration Zytex posted is also pass by value.


type Dog = record
Speak: String;
end;

procedure foo(const D: Dog);
begin
writeln(D);
//D.Speak := 'Meow';
end;

procedure bar(D: Dog);
begin
writeln(@D);
D.Speak := 'Meow';
end;

procedure meh(var D: Dog);
begin
writeln(@D);
D.Speak := 'Meow';
end;

procedure bleh(D: ^Dog);
begin
writeln(D);
D^.Speak := 'Woof';
end;


var
D: Dog;
begin
D.Speak := 'Woof';

foo(D);
bar(D);
meh(D);
bleh(@D);
writeln(D);
end.

stata
09-01-2013, 06:20 PM
Uhh I don't think that const is a pointer. In fact, in C and many C-style languages, a const parameter is useless because either way the function will make a copy of the parameter passed and work on that.

Basically the equivalent of doing:


void func(const int I)
{
//work with that copy of I here..
}

void func(int I)
{
//work with the copy of I here..
}


Either way, it is still a copy with or without the const. So his first and last declarations seem to be the same to me :S Your example:


Procedure Foo(a: ^TIntegerArray);
begin
//work with pointer "a" here..
end;

var
a: TIntegerArray;
begin
a := [1, 2];
Foo(@a);
end.


seems to be right afaik. Unless lape defined the usage of the keyword const wrong, I think that the third example is also pass by value.

Makes sense to me, thanks again

Zyt3x
09-01-2013, 06:20 PM
obviously there are multiple methods of declaration... that's why I'm asking

that last declaration confuses me. Is that the same as this
procedure bob(a: ^TPointArray);

seems like an odd way to send a pointer, particularly since it seems to imply that "a" cannot be changed by the procedure

and do "in" and "out" work as they did in PS? I'm aware of those...I understood you know there's multiple methods, but you asked, so I answered.

As for the "procedure bob(const a: TPointArray);". You are correct in that it can't be changed by the procedure:
procedure bob(const a: TPointArray);
var
b : TPointArray;
begin
setLength(a, 0); // Will fail because a is not directly editable.
b := a; // Will not fail as a is not edited.
end;The only reason to do this is (as you probably already know) to spare memory.

As far as I am aware, "in" and "out" works just like PS. I rarely use them with Simba, to be honest.

stata
09-01-2013, 06:32 PM
I understood you know there's multiple methods, but you asked, so I answered.

As for the "procedure bob(const a: TPointArray);". You are correct in that it can't be changed by the procedure:
procedure bob(const a: TPointArray);
var
b : TPointArray;
begin
setLength(a, 0); // Will fail because a is not directly editable.
b := a; // Will not fail as a is not edited.
end;The only reason to do this is (as you probably already know) to spare memory.

As far as I am aware, "in" and "out" works just like PS. I rarely use them with Simba, to be honest.

Sorry, just was getting a little tired of being "talked down" to as if I was an idiot. I do appreciate the helpful nature of the community here though.

Yeah, I also rarely use in and out as well, but good to know

Zyt3x
09-01-2013, 07:20 PM
Sorry, just was getting a little tired of being "talked down" to as if I was an idiot. I do appreciate the helpful nature of the community here though.

Yeah, I also rarely use in and out as well, but good to knowI'm sorry; that was not at all the intention of my post (!)
I think you'll have to look for a while in order to find nicer communities, tbh ;) Welcome here :)

Frement
04-04-2014, 03:00 PM
Brandon;

How would I do something like this:
type
PTest = ^TTest;

TTest = record
__object: Integer;
__parent: PTest;
end;

function TTest.create(obj: Integer): TTest;
begin
result.__object := obj;
if (obj <> 100) then
begin
result.__parent := @TTest.create(100);
end;
end;

function TTest.test: Integer;
begin
writeln(self.__parent^);
result := self.__parent^.__object;
end;

var
test: TTest;

begin
test := TTest.create(50);
writeln(test.__object);
writeln(test.__parent^);
writeln(test.__parent^.__object);
writeln(test.test());
end.
As it is currently, the pointer to the parent is invalid after exiting TTest.create function.

masterBB
04-04-2014, 03:15 PM
As it is currently, the pointer to the parent is invalid after exiting TTest.create function.

These aren't objects like in java/c++. I think that when the new created TTest goes out of scope it will be gone.

Frement
04-04-2014, 03:26 PM
These aren't objects like in java/c++. I think that when the new created TTest goes out of scope it will be gone.

Yes, and I need a solution for that issue :)

Zyt3x
04-04-2014, 04:32 PM
Frement; You could take a look in the SRL-6 include if you want to discover some of lape's functionality.
type
PTest = ^TTest;
TTest = record
__object: Integer;
__parent: PTest;
end;

procedure TTest.create(obj: Integer; parent : PTest);
begin
self.__object := obj;
self.__parent := parent;
end;

function TTest.test: Integer;
begin
writeln(self.__parent^);
result := self.__parent^.__object;
end;

var
test: TTest;
parent : TTest;

begin
parent.create(50, nil);
test.create(100, @parent);
writeln(test);
writeLn(test.__parent^);
end.

Compiled successfully in 703 ms.
{__OBJECT = 100, __PARENT = "parent"::0x96B9AD0 ({__OBJECT = 50, __PARENT = nil})}
{__OBJECT = 50, __PARENT = nil}
Successfully executed.

Frement
04-04-2014, 04:35 PM
Frement; You could take a look in the SRL-6 include if you want to discover some of lape's functionality.
type
PTest = ^TTest;
TTest = record
__object: Integer;
__parent: PTest;
end;

procedure TTest.create(obj: Integer; parent : PTest);
begin
self.__object := obj;
self.__parent := parent;
end;

function TTest.test: Integer;
begin
writeln(self.__parent^);
result := self.__parent^.__object;
end;

var
test: TTest;
parent : TTest;

begin
parent.create(50, nil);
test.create(100, @parent);
writeln(test);
writeLn(test.__parent^);
end.

Compiled successfully in 703 ms.
{__OBJECT = 100, __PARENT = "parent"::0x96B9AD0 ({__OBJECT = 50, __PARENT = nil})}
{__OBJECT = 50, __PARENT = nil}
Successfully executed.

I did try to look in SRL-6 but didn't find anything I could apply to this situation, and the code you have there, isn't what I want, the create() function needs to create the parent.

Brandon
04-04-2014, 05:16 PM
Yes, and I need a solution for that issue :)


There is simply undefined behaviour by accessing stack allocated objects.. You need a method of allocating on the heap while not destroying garbage collection. Your structure looks like a singly-linked-list but that isn't possible in Simba without hacks/tricks.

Problem:

type
PTest = ^TTest;

TTest = record
__object: Integer;
__parent: PTest;
end;

Function TTest.Create(Obj: Integer): TTest;
Begin
Result.__object := Obj;
writeln('Constructed: ', Obj);

If (Obj <> 100) Then
Result.__parent := @(TTest.Create(100));

writeln('My parent is: ', Result.__Parent);
Writeln('Unwinding..');
End;


var
test: TTest;
begin
test := TTest.Create(50);
writeln();

writeln('Parent value: ', test.__parent^.__object);
writeln('Parent: ', test.__parent^);
writeln('Parent value: ', test.__parent^.__object);
end.



Compiled successfully in 203 ms.
Constructed: 50
Constructed: 100
My parent is: nil
Unwinding..
My parent is: 0x911B850 ({__OBJECT = 100, __PARENT = nil})
Unwinding..

Parent value: 100
Parent: {__OBJECT = 100, __PARENT = nil ({__OBJECT = 543975790, __PARENT = 0x43450028})}
Parent value: 152156224 //UNDEFINED BEHAVIOUR..
Successfully executed.


In the above, Test.Create is called recursively. The first call is to create the current object that will be returned. If the parameter is NOT 100, then the stack is pushed down to save the return value. Finally, it calls itself again to allocate an object on the current stack. When the unwind section is hit, the stack unwinds/pops and the object created is destroyed. It should print nil.

When unwind is called again, the value of __parent should be undefined/random/nil because the pushed down stack is now popped and the result is returned. That result has nothing allocated to it. It isn't initialized to nil or 0 either..


Why does the object get destroyed during stack unwinding? Stack unwinding is how functions destroy local variables.. The memory is reclaimed upon stack unwinding. This is why we never have to "free" stack variables. This technique is known as setting up a stack-frame in assembly.


The trick to getting around this is to allocate memory on the heap. Memory on the heap lives until it is freed by the user manually.

There is ONLY ONE way to do that in Lape (dynamic arrays) and since there is garbage collection, even heap allocations get destroyed when their reference count reaches 0.

So how do we trick/hack this into leaving the memory alone? Well.. we simply allocate a large enough byte array and set is reference count to more than 1 so that it survives the garbage collection upon stack unwinding.. We cast this memory to our structure type and store what we want in it. When the stack unwinds once, the reference count will drop to 1. When the script terminates, that reference count will hit 0 and the heap will be de-allocated automatically. Rarely should you ever use a reference count greater than 2! If you do, you must free the heap allocation yourself!

With the above technique of keeping references at a max of 2, we don't leak memory AND we get to keep garbage collection without messing stuff up as well as surviving stack unwinding..


Solution:

type
PTest = ^TTest;

TTest = record
__object: Integer;
__parent: PTest;
end;



Function HeapAlloc(Size, ReferenceCount: UInt32): Array Of Byte;
type
PUInt32 = ^UInt32;
Begin
SetLength(Result, Size);
(PUInt32(@Result[0]) - (sizeof(UInt32) * 2))^ := ReferenceCount;
End;

Procedure HeapFree(var Arr: Array Of Byte); //Only call if reference count > 2.
type
PUInt32 = ^UInt32;
Begin
(PUInt32(@Arr[0]) - (sizeof(UInt32) * 2))^ := 0;
SetLength(Arr, 0);
End;

Function TTest.Create(Obj: Integer): TTest;
var
P: ^Byte;
Begin
Result.__object := Obj;
writeln('Constructed: ', Obj);

If (Obj <> 100) Then
begin
P := @HeapAlloc(SizeOf(Self), 2)[0]; //Will only survive stack deallocation ONCE! 2 - 1..
PTest(P)^ := TTest.Create(100);
Result.__parent := PTest(P);
end;

writeln('My parent is: ', Result.__Parent);
Writeln('Unwinding..');
End;


type
PUInt32 = ^UInt32;
P = Array of Byte;
PP = ^P;

var
test: TTest;
begin
test := TTest.Create(50);
writeln();

writeln('Parent value: ', test.__parent^.__object);
writeln('Parent: ', test.__parent^);
writeln('Parent value: ', test.__parent^.__object);

writeln('Reference Count: ', (PUInt32(test.__parent) - (sizeof(Int32) * 2))^); //Should print 1 before script termination! Otherwise call HeapFree!
end.



Compiled successfully in 719 ms.
Constructed: 50
Constructed: 100
My parent is: nil
Unwinding..
My parent is: 0x8EA9058 ({__OBJECT = 100, __PARENT = nil})
Unwinding..

Parent value: 100
Parent: {__OBJECT = 100, __PARENT = nil}
Parent value: 100
Reference Count: 1
Successfully executed.



Additional sweetness to the allocation and de-allocation routines.. Instead of returning array of bytes and having to cast, you can return an underlying opaque pointer to the memory:


Function HeapAlloc(Size, ReferenceCount: UInt32): Pointer;
type
PUInt32 = ^UInt32;
var
Memory: Array Of Byte;
Begin
SetLength(Memory, Size);
(PUInt32(@Memory[0]) - (sizeof(UInt32) * 2))^ := ReferenceCount;
Result := @Memory[0];
End;

Procedure HeapFree(var Memory: Pointer); //Only call if ReferenceCount > 2.
type
PUInt32 = ^UInt32;
Ptr = Array Of Byte;
Begin
(PUInt32(@Ptr(Memory)[0]) - (sizeof(UInt32) * 2))^ := 0;
SetLength(Ptr(Memory), 0);
End;

Function TTest.Create(Obj: Integer): TTest;
var
Mem: Pointer;
Begin
Result.__object := Obj;
writeln('Constructed: ', Obj);

If (Obj <> 100) Then
begin
Mem := HeapAlloc(SizeOf(Self), 2);
PTest(Mem)^ := TTest.Create(100);
Result.__parent := PTest(Mem);
end;

writeln('My parent is: ', Result.__Parent);
Writeln('Unwinding..');
End;


NOTES: You might never ever actually have to call HeapFree. If you think about it, no matter what amount of reference counts an array has, it is always freed in Lape. That function is just there for safety and paranoia.

Frement
04-04-2014, 05:49 PM
Yeah, exactly what I wanted, thanks :) Also your explanations are always so fully detailed.

Dmox
04-07-2014, 03:40 AM
Really nice tutorial, will look into it later!

honeyhoney
06-28-2014, 06:13 PM
This was really useful, thank you.

The discussion on methods of declaration was especially useful. :)
It definitely dug me out of a hole!

SimbaOp
10-04-2016, 01:18 AM
nice

poobear12
10-04-2016, 11:53 AM
This is an AWESOME tutorial. Thank you so much. :garfield:

Joopi
10-04-2016, 01:32 PM
What are Records poobear12;

Edit: Good job, you're learning!

poobear12
10-04-2016, 04:43 PM
What are Records poobear12;

Records as far as I understand are information blocks that hold information blocks. Kind of like storing books in a book shelf for more logical ordering of info. :p

This is in my own words! I hope you like my explanation haha.