If you wanted a single procedure to perform a complex task, the procedure would become long and complicated. It is more convenient to have a module containing a number of procedures, each of which you can write and edit separately.
Many OPL modules are in fact a set of procedures linked up each procedure doing just one job (such as a certain calculation) and then passing its results on to other procedures so they can do other operations: (picture)
OPL is designed to encourage programs written in this way, since:
You can have as many procedures as you like in a module. Each must begin with PROC and end with ENDP.
When you run a translated module it is always the first procedure, at the top of the module, which is actually run. When this finishes, the module stops; any other procedures in the file are only run if and when they are called.
Although you can use any name you want, it's common to give the first procedure a name like "start".
Procedures which run on their own should be written and translated as separate modules, otherwise you won't be able to run them.
To run another procedure, simply give the name of the procedure (with the colon). For example, this module contains two procedures:
PROC one: PRINT "Start" PAUSE 40 two: REM calls procedure two: PRINT "Finished" PAUSE 40 ENDP
PROC two: PRINT "Doing..." PAUSE 40 ENDP
Running this module would run procedure "one:", with this effect: "Start" is displayed; after a PAUSE it calls "two:", which displays "Doing..."; after another PAUSE "two:" returns to the "one:" procedure; "one:" displays "Finished"; and after a final PAUSE, "one:" finishes.
Calling procedures can be used to:
PROC input: LOCAL a$(1) PRINT "Add [A] or Subtract [S]?:", a$=UPPER$(GET$) IF a$="A" add: REM first procedure ELSEIF a$="S" subtract: REM second procedure ENDIF ENDP
To make full use of procedure calls, you must be able to communicate values between one procedure and another. There are two ways of doing this: global variables and parameters.
Values can be passed from one procedure to another by using parameters. They look, and act, very much like arguments to functions.
In the example below, the procedure "price:" calls the procedure "tax:". At the same time as it calls it, it passes a value (in this case, the value which INPUT gave to the variable "x") to the parameter "p" named in the first line of "tax:". The parameter "p" is rather like a new local variable inside "tax:", and it has the value passed when "tax:" is called. (The "tax:" procedure is not changing the variable "x".)
The "tax:" procedure displays the value of "x" plus 17.5% tax.
PROC price: (picture) LOCAL x PRINT "ENTER PRICE", INPUT x tax:(x) REM Passes the value of x to p GET ENDP
PROC tax:(p) PRINT "PRICE INCLUDING TAX =",p*1.175 ENDP
In the called procedure, you cannot assign values to parameters for example, if "p" is a parameter, you cannot use a statement like "p=10".
You will see a `Type mismatch' error displayed if you try to pass the wrong type of value to a parameter for example, 45 to (a$).
In the following example, the second procedure "tax2:" has two parameters:
PROC price2: (picture) LOCAL x,r PRINT "ENTER PRICE", INPUT x PRINT "ENTER TAX RATE", INPUT r tax2:(x,r) GET ENDP
PROC tax2:(p1,p2) PRINT p1+p2 % ENDP
This uses the "%" symbol as an operator "p1+p2 %" means "p1" plus "p2" percent of "p1". Note the space before the "%"; without it, "p2%" would be taken as representing an integer variable.
Appendix B has more about the "%" operator.
In the following example, the RETURN command is used to return the value of "x" plus tax at "r" percent to be displayed in "price3:". This is very similar to the way functions return a value.
The "tax3:" procedure calculates, but doesn't display the result. This means it can be called by other procedures which need to perform this calculation but do not necessarily need to display it.
PROC price3: (picture) LOCAL x,r PRINT "ENTER PRICE", INPUT x PRINT "ENTER TAX RATE", INPUT r PRINT "PRICE INCLUDING TAX =",tax3:(x,r) GET ENDP
PROC tax3:(p1,p2) RETURN p1+p2 % ENDP
Only one value may be returned by the RETURN command.
The name of a procedure which returns a value must end with the correct identifier "$" for string, "%" for integer, or "&" for long integer. To return a floating-point number, it should end with none of these symbols. For example, "PROC abcd$:" can return a string, while "PROC counter%:" can return an integer. In this example, "ref$:" returns a string:
PROC refname: LOCAL a$(30),b$(2) PRINT "Enter reference and name:", INPUT a$ b$=ref$:(a$) PRINT "Ref is:",b$ GET ENDP
PROC ref$:(name$) RETURN LEFT$(name$,2) REM LEFT$ takes first 2 letters of name$ ENDP
If you don't use the RETURN command, a string procedure returns the null string (""""). Other (numeric) types of procedure return zero.
You can only return one value with the RETURN command. If you need to pass back more than one value, use GLOBAL variables.
Instead of declaring "LOCAL x%,name$(5)" declare "GLOBAL x%,name$(5)". The difference is that:
PROC one: GLOBAL a% PRINT a% two: GET ENDP
PROC two: a%=2 REM Sees a% declared in one: PRINT a% ENDP
When you run this, the value 0 is displayed first, and then the value 2.
You would see an `Undefined externals' error displayed if you used LOCAL instead of GLOBAL to declare "a%", since the procedure "two:" wouldn't recognise the variable "a%". In general, though, it is good practice to use the LOCAL command unless you really need to use GLOBAL.
A local declaration overrides a global declaration in that procedure. So if "GLOBAL a%" was declared in a procedure, which called another procedure in which "LOCAL a%" was declared, any modifications to the value of "a%" in this procedure would not effect the value of the global variable "a%".
You can effectively pass as many values as you like back from one procedure to another by using global variables. Any modifications to the value of a variable in a called procedure are automatically registered in the calling procedure.
For example:
PROC start: GLOBAL varone,vartwo varone=2.5 vartwo=2 op: PRINT varone,vartwo GET ENDP
PROC op: varone=varone*2 vartwo=vartwo*4 ENDP
This would display "5 8"
If, perhaps because of a typing error, you use a name which is not one of your variables, no error occurs when you translate the module. This is because it could be the name of a global variable, declared in a different procedure, which might be available when the procedure in question was called. If no such global variable is available, an `Undefined externals' error is shown. This also displays the variable name which caused the error, together with the module and procedure names, in this format: `Error in MODULE\PROCEDURE,VARIABLE'.
Call a procedure by stating its name, including the colon.
Pass parameters to a procedure by following the procedure call with the values for the parameters, eg "calc2:(4.5,32)". In the called procedure, follow the procedure name with the parameter names, eg "PROC calc2:(mod,div%)".
To make variables declared in one procedure accessible to called procedures, declare the variables with GLOBAL instead of LOCAL.