Morfik Pascal has the same subroutine constructs as all the major Pascal implementations available and then some more. Some additions were made to the Morfik Pascal language in order to fully support the underlying Morfik language semantics.
Types of Subroutines
In Morfik Pascal there are two fundamental types of subroutines: Procedures and Functions. The difference between a Procedure and a Function is whether or not the routine returns a value.
Procedures are subroutines that, by definition, do not return values, (except via input parameters passed by reference, which is explained later). Since they do not return a value, procedures cannot be part of expressions.
The following is a short example of a Procedure:
Procedure Test; Var F2 : file of Integer; Begin AssignFile(F1, 'c:\test.int'); Rewrite(F1); Write(F1, 9); CloseFile(F1); End;
Functions are subroutines that do return values. Because of this, functions can be used within expressions where you would normally use a literal value.
The following is a short example of a function:
Function GetTestString : String; Begin Result := 'Test!'; End;
Returning values from a function can be done in two different ways: through the Result variable or by assigning a value to the name of the function itself.
In the previous example you can see the use of the Result variable which takes the following form:
Result := <value>;
You can assign the value you want to return from a function to the function’s name, as if it was a variable. This takes the following form:
FunctionName := <value>;
If you have chosen to use this method, you cannot read back the value assigned to the function name. This would be interpreted by the compiler as actually being a recursive call to the function. In order to get around this limitation, common practice is to create an intermediate variable which will hold the value until such time as you are ready to exit the function, keeping the value available for reading. In order to simplify and standardize the usage of this pattern, Morfik Pascal uses the Result variable. Assigning a value to the result variable is essentially the same as assigning it to the name of the function but at any time you can use Result in the right side of an expression, without initiating a recursive call.
Passing parameters to subroutines is done through a list which is constructed in a very specific way.
Creating a Parameter List
A subroutine parameter list is very similar to a sequence of variable declarations. This can be observed in the following example.
Procedure Test(Param1 : string; Param2 : integer);
This simple example shows a parameter list with two parameters of different types. Morfik Pascal has a number of modifiers which can be applied to parameters to change how they are handled by the compiler. The following is a sample of all possible forms of declaring parameters.
Procedure Test(pValueParameter1 : Integer, Const pConstParameter : Integer, Var pVarParameter : Integer, Out pOutParameter : Integer, Const pConstUntypedParamter, Var pVarUntypedParameter, Out pOutUntypedParameter) Begin End;
There are basically five different kinds of parameters:
1. By Value
3. By Reference
4. Output only
5. Untyped parameter
Parameters can be categorized in the following way:
- Any parameter which does not have a Var, Const or Out modifier can be considered a parameter passed by value.
- Any parameter which has no type definition can be considered as an untyped parameter.
Parameters Passed By Value
A parameter passed by value acts as a variable local to the subroutine to which it belongs. The parameter is initialized to the value passed to the subroutine and though it can be altered inside the routine, the new value will not persist after the code exits the subroutine.
Procedure ABC(Param1 : integer); Begin Param1 := Param1 * 2; End;
If no modifier is specified for a parameter it is presumed by the Morfik Pascal compiler to be a parameter passed by value.
Var I : integer; Begin I := 2; ABC(I); CallOtherProc(I);
The value of variable "I" when the call to CallOtherProc is reached will be 2. The value of the original variable has not been altered since the ABC routine worked on a copy of the value held by the original value By value parameters can receive the result of any expression that yields a compatible value as can be seen in the following example.
ABC ( (3+6) * 3-1 );
Constant parameters are very similar in usage to value parameters but they cannot be altered within the subroutine. The main purpose of constant parameters is to serve as insurance to the developer that when a parameter is not meant to be altered, it is not.
|Note:||In Morfik 07 the Morfik Pascal compiler will not immediately complain if you try to alter a constant parameter within a subroutine. When you do a full compile of the project, however, the backend platform compiler will complain.|
Parameters Passed By Reference
Parameters passed by reference, using the Var modifier, should be used whenever a subroutine needs to return a modified value for one or more of its parameters. By reference parameters can only be called with the use of variables since any literal expression is evaluated to a value and would not be addressable in memory to have its content changed.
Usage of by reference parameter within a subroutine is exactly like that of a by value parameter, except that the value of the variable which was passed as a parameter will be altered once the execution of the routine concludes. The following is an example of passing a parameter by reference:
Procedure ABC(var Param1 : integer); Begin Param1 := Param1 * 2; End;
In this case if we applied the same code that we used to review the workings of by value parameters we would have a different result.
Var I : integer; Begin I := 2; ABC(I); CallOtherProc(I);
The value of variable "I" when the call to CallOtherProc is made will actually be 4, since inside the subroutine the value of 2 was multiplied by two and that result was returned in the parameter declared with the Var modifier.
Output-only parameters are specifically for returning more than one value from a subroutine. For returning a single value the norm is to just use a function. When there is a need to return more than one value from a subroutine, multiple output-only parameters should be used. Output-only parameters are declared using the Out modifier. Any value the subroutine receives in an Out parameter is discarded.
When a parameter does not have a predefined data type it can receive any variable or constant, it cannot receive a literal value, though, as untyped parameters can only be declared as Var, Const or Out. Morfik Pascal does not allow for By Value un-typed parameters.
Untyped parameters are passed as memory addresses and it is up to the developer to know how to handle them within the subroutine.
|Note:||An untyped parameter can be used as a way of being able to pass parameters of different types to a subroutine. In many situations the same results can be achieved by using different overloaded versions of a Procedure or Function. Though there are situations where you could not accommodate all the necessary data types with overloaded functions, their usage offers the added benefit of additional type checking.|
For more information see Overloading Subroutines later in this chapter.
External subroutines can be brought into your Morfik Pascal universe from the Browser or the intermediate code level on the server side of your application. External subroutines are declared with the assistance of compiler metadata attributes. The following is an example how a server side external routine can be imported into your code:
Function IntToStr(AnInteger : integer) : string; ['external=systemext.pas']
In Morfik Pascal it is possible to have several different subroutines with the same name, as long as they have different parameter lists. This is called Overloading and can be really useful. Overloaded subroutines must be marked with the overload modifier. The following is an example of how to use subroutine overloading.
Function toString(AnInt : Integer) : String; Overload; Begin Result := IntToStr(AnInt); End; Function toString(AFloat : Double) : String; Overload; Begin Result := FloatToStr(AFloat); End;
|Note:||The addition of Overloading capabilities to the underlying Morfik semantic structure, which is the basis for Morfik Pascal, was done at the end of the product development cycle, for Morfik 07. This means that this feature is not exploited to its full potential by the Morfik Framework itself.|
In future releases of Morfik you will, probably, see this feature being more widely used.
Nested Subroutines and Data Types
Morfik Pascal fully supports nesting subroutines within one another. You can also have nested data types, i.e. data types which are declared within a subroutine. The following example shows how the syntax for these two features is implemented in Morfik Pascal.
Procedure Test1(Param1 : integer); //Nested data type declaration Type PInteger = ^integer; //Nested subroutine Procedure Test2(Param2 : String); Begin ShowMessage(Param2); End; Var P : Pinteger; Begin P := Param1^; Test2(IntToStr(P^)); End;
A reference to an identifier, within an application, must happen within that identifier’s declaration scope. The following are the main scope definition rules for Morfik Pascal.
Subroutine (Function/Procedure) Scope
Subroutine scope is defined by the boundaries of the subroutine itself. Any variables, data types, and nested subroutines it declares will be visible only from within that subroutine. A nested subroutine will have access to any identifier declared prior to its declaration, within the surrounding subroutine.
A nested subroutine can define an identifier with the same name of an identifier declared in the subroutine in which it is nested. The innermost variable will then hide the outermost one.
An identifier declared within a class is visible to all members of the class and its descendants. This scope included instance variables, methods and properties. The Subroutine Scope rules apply to all method declarations.
Identifiers (variables, data types, subroutines, classes) declared in the implementation section of a module are visible within the module in which they are declared. They will be visible to any subroutines and classes declared within the same module.
Identifiers (variables, data types, subroutines, classes) declared in the interface section of a module are visible within the module in which they are declared and in any module that imports that module. They will be visible to any subroutines and classes declared within the same module and within the modules that have imported the module in which they are declared.
This is the broadest scope level in Morfik Pascal.
In Listing 1 you can see examples of all the possible scope levels in Morfik Pascal.
Listing 1 – Possible Scopes in Morfik Pascal
Unit MyModule; // Module/Public Scope Interface // Whatever is defined in the interface has public scope (** MyInt is available within this module and in any module that imports this module, since it is declared in the interface section. (Public Scope) *) var MyInt : integer; Type AClass = class // Class Scope (** Class level MyInt is available only within this class since it is declared private. Within the class it hides the module scope MyInt *) Private MyInt : integer; (** Method OuterFunc defines a new scope *) Public Function AClass.OuterFunc : String; //Method Scope End; // class definition (** GlobalProc is available within this module and in any module that imports this module because it is declared in the interface section.. *) Procedure GlobalProc; //Function/Procedure Scope Implementation (** AnotherInt is available only within this module since it is declared in the implementation section. (Module Scope) *) Var AnotherInt : integer; (** Method OuterFunc defines a new scope *) Function OuterFunc : String; //Method Scope (** Method level MyInt is available only within this method. Within the method it hides the Module and Class scope variables of the same name. *) Var MyInt : integer; Function NestedFunc : String; //Nested Function Scope (** Nested function level MyInt is available only within this function. Within the function it hides the Module, Class and method scope variables of the same name. *) Var MyInt : integer; Begin //Body of the nested function End; Begin //Body of the outer function End; (** GlobalProc is available within this module and in any module that imports this module because it was previously declared in the interface section. *) Procedure GlobalProc; //Function/Procedure Scope (** Procedure level MyInt is available only within this Procedure Within the Procedure it hides the Module scope variable of the same name. (Public Scope) *) Var MyInt : integer; Begin End; (** InternalProc is available only within this module since it is declared only in the implementation secion. (Module Scope) *) Procedure InternalProc; // Function/Procedure Scope Begin End; End.
Wrapping it up