This small article will teach you how to easily embed functions
by declaring functions as String variables within a function
0. IntroLast week i delved into the functionality of ofp functions
in order to check whether synchronization within a function was possible
with the use of so-called semaphores. Unfortunately, it was not.
I discovered, though some interesting features, concerning function syntax:
1. When to use semi colons (";")Semi-colons are used in programming languages to delimit statements.
Statements are the smallest atomic entities within a function or method,
which hold relevant information
eg. private {_variable1, _variable2};In ofp, however, semi-colons seem to be used in a quite different way than in
a programming language like Java:
1.1. code blocks (between curly brackets) like while ... do, if ... then ... else
structures have to be delimited by a semi-colon if you want stuff to be processsed
by your function after the code block.
eg.
while {} do {
... };
hint "Stuff after a code block";1.2. statements returning values are NOT to be ended with a semi-colon!
apparently the compiler returns the value of "the last statement" (as is stated in
the BIS documentation) make sure this statement is never delimited.
In the case it is delimited, the stuff behind the semi-colon -nothing-, will be returned,
and you will get a value that looks like a list of all possible data types ...
eg.
suppose you want to return a value from an if/else structure:
this will not work
if (_variable1 < _variable2) then {
0; }
else {
1;}this will work
if (_variable1 < _variable2) then {
0} // no semi-colon
else {
1} // no semi-colon1.3. My hypothesis: ofp functions work with a stack,
When a function returns, it returns the top of the function stack.
A position in the function stack is delimited by a semi-colon BUT, in order not to have
an empty stack with a resulting stackEmpty error there is always a first
element which should not be delimited. The consequence is, when you
delimit a statement, you actually push a new statement, containing nothing
onto the stack. The stack top remains empty until you push a new statement
on it. This explains why a returning value should not be delimited ...
CONCLUSION: the semi-colon is not really a delimiter in the sense that it ends
statements: instead, it marks the beginning of a new stack position
2. Embedded functionsAfter having learnt these stack-related things, I thought it had to be possible
to take advantage of this feature: in theory each subroutine (function)
has a call returning the top of a function stack (being either noting, or a value)
and each subroutine F2 is pushed on the stack of routine F1 (which I will call the
mother function).
In order to be able to call embedded functions you should have a stack structure
like this:
Top | F1b | F2 | F1awith F1a + F1b = F1
2.1. Preprocessing filesIt worked for preprocessed files. Try something like this:
preprocess a function F2 and call it from a function F1. It will work
file F2.sqf will retun the value 10
/* begin F2*/
10
/* end */
file F1.sqf will call F2 correctly
/* begin F1*/
_F2 = preprocessFile "F2.sqf";
hint format ["The embedded function F2 returns value %1", [] call _F2];
/* end */2.2. Declaring functions as a string variableBut here comes the most exciting part: knowing that functions are pushed on stacks,
and knowing that string variables can be declared both by hyphens ("") and curly
brackets, and knowing that you can load the contents of a file both with the commands
preprocessFile and loadFile, taking a string as an argument, you can infer that a function itself
is nothing more than a String which is processed into a stack structure.
So I tried this:
file script.sqs
;begin of a script file
_function = preprocessFile {function.sqf}
_result = - call _function[/i]
hint _result
file function.sqf
/* begin */
_variable1 = _this select 0;
_method1 = {
_variable1 = _this select 0;
_variable1
};
_variable1 + [1] call _method1
/* end */
Explanation:
1. I call _function from a script with argument 0 in a file script.sqs
0 will be passed as the first _this select 0 and will be stored in the first _variable1
2. I declare a new function, called _method1, as a String (remember that a String can be
declared both with hyphens an dcurly brackets). I delimit this declaration (since it is an
element of _function and it is not the return statement.
3. The syntax of String variable _method1 is completely similar to the syntax of
a non-embedded function: this means: delimiters for non-returning statements and
no delimiter for returning statements.
4. Note however that I used twice the same variables: _this select 0 and _variable1
both in _function and in _method1. Since functions are pushed on stacks _method1
only should have acces to its own _variable1 and _this select 0.
5. I ran the function and to my surprise (and expectation) it worked ...
3. Possibilities
You now can reuse code even more efficiently: suppose you need an operation
within a function several times. All you have to do is declare a new function as a String,
before you use it (ofp compilation is still line-by-line) and call it when you need it ...
4. Some remarks
4.1. Do not use tabs to indent your code: it doesn't work
4.2. Commenst and newlines work fine
4.3. If enough people are interested I will re-explain this matter in an article