|
|
|
QDL has very powerful function-calling features, including:
Some of this stuff is so advanced, it can't be covered in this chapter!
Here is the basic syntax of a function declaration/definition:
function-declaration-statement: function-signature;
function-statement: function-signature Do statement*
End [Function | Operator | Cast
| Property];
function-signature: function-modifiers (Function | Operator
| Cast | Property) [member-scope][function-identifier][=]
[(argument-list)] [: return-type-spec]
function-moifiers: [Const | Static | Final
| Abstract | Inline | Override |
New | attribute-set]*
argument-list: [string-delimiter] [argument-list-component [delimiter
argument-list-component]* [string-delimiter]]
argument-list-component: normal-argument | abstract-argument | automatic-argument
normal-argument: (variable-list | type-spec [initialize-spec])
abstract-argument: Abstract (variable-list | )
automatic-argument: Automatic (variable-list | type-spec [initialize-spec])
delimiter: (string-delimiter | ;)
string-delimiter: "delimit-identifier[ delimit-identifier]*"
delimit-identifier: (identifier | [identifier])
Notes:
The function-identifier and delimit-identifier are identifiers; QDL identifier names must follow similar rules to BASIC, Pascal, C and C++ identifiers. Specifically:
As with all tokens in QDL (except, you might say, string and character literals), QDL is case-insensitive in dealing with identifiers.
At the beginning of the function-signature is zero or more of the modifiers Const, Static, Final, Abstract, Inline, New, and Override; the following applies:
The meanings of the modifiers can be summarized as follows:
You are not expected at this point to understand the references made to classes; the discussion of classes starts here.
Here are some examples of function declarations (functions without bodies). Following each declaration is an example function call.
Function OpenFile (FileName: String "For" Mode: OpenModes); // Openfile "C:\\Autoexec.bat" For Input Function Size(): Integer; // Size, Size() Function Size=(NewSize: Integer); // Size = 10, Size() += 10 Static Operator + (LHS: SomeClass; RHS: SomeClass): SomeClass; // Something1 + Something2 Operator [] (Index: Integer): @ SomeClass; // Object[10]
Variable lists, which contain type specs, are used in several places in QDL. In addition to function signatures, they are also found in:
Similar-looking constructs (that are not variable lists) are found in the use of the Template and Reference clauses of the Class statement. (which will be described later.)
Here is their syntax:
variable-list: (<identifier> | identifier...):
type-spec [initialize-spec]
type-spec: ( @type-spec | dimension-spec type-spec
| resolvedtype )
resolvedtype: (resolved-typeidentifier | (resolved-typeidentifier))+
resolved-typeidentifier: ([::][typeidentifier::]*typeidentifier
| @function-signature)
dimension-spec: [(elementcount-int-const-expr | lbound-int-const-expr..[ubound-int-const-expr])]*
initialize-spec: ( (constructor-arg-list)
| initialize-list | empty-initializer )
empty-initializer: ( )
The syntax for an initialize-list can be found here.
Note: The variable-list format that uses ... is only allowed in function headers.
Note: The use of empty-initializer is only allowed within a Class.
And here are some simple examples:
X: Integer /* Declare an integer named X */
Y, Z: [10] Integer /* Declare two arrays of ten integers, named X and Y */
R: [2][3] Double /* Declare a two-dimensional array with 2 rows, 3 cols */
S: String("Hello") /* Declare a string with an initial/default value */
Args: @[List] String /* Declare a reference to a dynamic array of strings */
R2A: @[] Integer /* Declare a reference to an array of integers */
AoR: [] @Integer /* Declare an array of references to integers */
R2AoR: @[] @Integer /* Declare a reference to an array of references to integers */
FuncRef: @ Function (X: Integer) /* Declare a reference to a non-static function
in a class, that takes an integer, and returns nothing. */
When declaring a reference to a function, only one modifier applies in the function signature: Static. This modifier must be present to store references to Static functions (including global functions.), and must not be present to store references to non-Static functions.
The member-scope part of the function declaration specifies of what class the function is a member. For example, if the function is called DoStuff, and is a member of a class called LittleClass, which is inside a class called BigClass, the member-scope would be BigClass::LittleClass::.
There are a couple of places where the function-identifier may be omitted. In these places, the last :: on the end of the member-scope should not be omitted.
When you don't use QDL-specific syntax features, calling a QDL function is the same as calling a function in BASIC. Take the following function, for example:
Function MyFunc (X, Y: Integer): String Do Result: String; Result = (X + " " + Y); StdOut.Print Result; Return Result; End;
This function converts two integers to strings, concatenates them, prints the result, and returns the result.
To call this function and discard the return value, you must use a function-call-statement. In such a statement, the brackets are omitted:
MyFunc 2, 3; // Result is "2 3"
To call the function and use the return value, you must use an expression-statement. In an expression-statement, the arguments to the function must be enclosed in brackets:
String = MyFunc (2, 3);
Rule: When the name of a function (and the delimiters at the start of the argument list, if any) is found at the beginning of a statement, the compiler assumes it is a function-call-statement rather than an expression-statement, even if the name of the function is followed by an opening bracket. This is to resolve ambiguities that can arise such as this one:
(Where you have a function called LValue that takes one argument and returns an lvalue:)
LValue (X) = 2;Does this mean that the lvalue returned should be set to 2, or that X should be set to 2 and passed to the function? This rule designates that the latter is true. If you want to accomplish the former, use:
(LValue (X)) = 2;This rule also applies to variables that are references to functions.
Exception: If the function in question has no arguments, no delimiters (optional or not), and no overloads, the statement should be considered an expression-statement.
Note: When a function takes no arguments and has no delimiters, the use of brackets in an expression-statement is optional. Unlike in C, the name of a function is not a pointer to its address; to take the address of a function F, use @Function(F). A function's pointer cannot be used to call the function; it must be dereferenced first.
A function's argment can be given a default value by using an initialize-spec. This is usually a set of arguments to the object's constructor. For example, if the function from the previous section used this signature:
Function MyFunc (X, Y: Integer(5)): String
Then the default values for X and Y would be 5. And unlike in C++, you can omit any of the arguments that have default values, not just arguments at the end. Thus, you could call the function in any of the following ways:
MyFunc; // Result: "5 5" MyFunc 1; // Result: "1 5" MyFunc 1,; // Result: "1 5" MyFunc , 2; // Result: "5 2" MyFunc 1, 2; // Result: "1 2"
You can also have a default return value. This just means that if your function lacks a Return statement at the end, or if you use a Return statement without specifying something to return, the default value will be used.
When declaring a variable to contain a reference to a function, the function signature cannot include a default return value. This is because the opening bracket that would otherwise define the default return value is interpreted as marking the beginning of the constructor argument for the reference variable.
You can use alternative delimiters to enhance the syntax of function calls. BASIC has statements with unusual syntaxes like this:
Line (1, 1)-(100, 100), vbRed
Just by looking at it, it is obvious that the result will be a red line drawn from (1, 1) to (100, 100). Wouldn't it be nice if we could write custom functions with syntaxes like this? While QDL can't let you do just any syntax your wild imagination can dream up, it lets you have custom delimiters, which are just identifiers to use instead of the usual commas that separate you arguments. For example:
Function Line ("[From]" X1, Y1: Integer "To" X2, Y2: Integer "[With] Color" RGB: Integer)
The text in quotes are the optional delimiters. If the delimiter is in square brackets, it is optional. Thus, you can call the function in any of these ways:
Line From 1, 1 To 100, 100 With Color 0xFF0000 Line From 1, 1 To 100, 100 Color 0xFF0000 Line 1, 1 To 100, 100 Color 0xFF0000
Delimiters are allowed to match most QDL reserved words. The File::Open function happens to do this:
Function Open (FileName: String "For" Mode: OpenModes): Boolean;
The delimiter specification may not contain escape sequences. Any attempt to put an escape sequence between the quotes of a delimiter results in an error. The following reserved words may not be used as delimiters: Function, Class. The main reason for this is so that you cannot confuse the compiler using something like
Function Inline ("Function" X: Integer): End;
Function Mwahaha ():
Var Q: Integer;
Inline Function (Q);
End;
When a symbol (S) in a call of a function matches both a delimiter (D) and the name of some other identifier (O) available in the scope of the place where the function is being called, the compiler will favor interpreting S as referring to D rather than O. The compiler will interpret S as D even if O would work under the circumstances. The logic for this is that the compiler can be forced to see S as referring to O by enclosing O in brackets. However, the compiler should detect possible ambiguities and raise warnings about them.
Examples:
Function Func1 ("[D]" V: Integer(0));
Function Func2 ("[D]" V: Integer);
Function Func3 ("[D]" V: Integer(0) "[D]");
Function Func4 ("[D]" V: @ Double "[D]");
Function Func5 (V: Integer(0) "D");
Function Func6 (V: Integer "[C]"); // Func6-1
Function Func6 ("[D] C"); // Func6-2
Function Main()
{
Var C, D: Integer(5);
Func1 D; // Calls Func1 with V=0 Func2 D D; // First D is delimiter, second is the variable Func3 D D; // Both Ds are delimiters, V=0 Func3 D (D); // First D is a delimiter, second is the variable Func4 D D; // One of the Ds is a delimiter, the other is the variable // (The compiler can choose either.) // This will raise an error because D is not a Double. Func5 D; // Calls Func5 with V=0; D must be interpreted as the delimiter Func6 D C; // D is seen as a delimiter, so Func6-2 is called Func6 D + C; // The binary + operator forces the interpretation of C and D as // variables; Func6-1 is called. }
Two functions can have the same name, yet not be in conflict. This is because the signatures can be different. The signature of a function is based on:
The signature is not affected by:
For example, the following set of functions live happily together:
Function MyFunc (X: Integer; Y: Integer(0)); // Function 1
Function MyFunc ("[Not] [Yours]" X: Integer; Y: Integer); // Function 2
Function MyFunc (X: Integer); // Function 3
However, I have built into this example a possible ambiguity that can appear. The compiler won't know what to do, and will issue an error, if you use the following statement:
MyFunc 10; // Which function to call, 1 or 3?
This ambiguity can be avoided by using this syntax instead:
MyFunc 10, ;
A second ambiguity arises with:
MyFunc 2, 3; // Which function to call, 1 or 2?
This ambiguity can be resolved by inserting Not and/or Yours before the first argument, but if you want to call function 1 with Y = 3 you're out of luck. In fact, in the previous example it's not even possible to call function 3 directly; however, the compiler does not have to realize such facts when the functions are declared; all it will do is make sure that the signatures are different.
As shown before, the delimiters can differentiate two functions with the same name. For example, the following functions are different:
Function Restroom ("[Men]"); // First function
Function Restroom ("Women"); // Second function
Restroom; // Statement 1: Calls the first function Restroom Men; // Statement 2: Calls the first function Restroom Women; // Statement 3: Calls the second function
Sometimes, ambiguity can arise when an optional delimiter is left out. For example, if "Women" in the second function was made optional, then Statement 1 would become ambiguous. Note that the compile error will occur when compiling Statement 1, and not when compiling the second function's declaration.
* The statement below may be inaccurate *
Note that you cannot take the address of an overloaded function, due to the fact that, without having arguments to the function, it is not apparent to the compiler which version of the function you want to get the address of.
The function signature is different from the function protocol. The function call protocol determines how functions are physically called, and is defined by the following:
Function references can be implicitly converted from one type to another if the function call protocols match. Here are a couple of examples:
Function Func1 (X: Integer; Y: Integer): Integer;
Function Func2 ("A" X: Integer "B" Y: Integer "[C]"): Integer;
Function Func3 (X: Integer; Y: UInteger): Integer;
Function Demo1 Do FuncRef: @Static Function (A, B: Integer) Returns Integer; @FuncRef = @Function (Func1); // OK @FuncRef = @Function (Func2); // OK, delimiters don't matter @FuncRef = @Function (Func3); // ERROR! Argument 2 is of different type End;
Class A: // Note: Class statement documentation starts here. Function Func1: Integer; Static Function Func2: Integer; End Class; Class B: Function Func1: Integer; End Class; Function Func4: Integer;
Function Demo2 Do A: A; B: B; Ref1: @Function : Integer; Ref2: @Static Function : Integer; @Ref1 = @Function (A.Func1); // OK @Ref1 = @Function (A::Func2); // Error: A::Func2 is Static @Ref1 = @Function (B::Func1); // Error: no object specified @Ref2 = @Function (Func4); // OK @Ref2 = @Function (A::Func2); // OK, static members compatible End;
A variable-argument list allows a function to recieve any number of arguments. To specify a variable-argument list, simply use Ident... in place of a list of variable names, where Ident is an identifier you want to use to refer to the argument list. Inside the body of the function, Ident becomes the name of a [Fixed List] array representing the arguments to the function. For example:
Function MyFunc (ArgArray...: Integer) Do For N: Integer(0); N < ArgArray.Count; N++ Do Print ArgArray[N], '\t'; End;
Here is an example function call with the resultant output:
MyFunc 2, 3, 5, 7, 11, 13
2 3 5 7 11 13
You can have more than one variable-argument list in a function, and you can overload functions that use variable-argument lists, but take care not to introduce ambiguity into the signatures. For example:
Function F (ArgArray1...: Integer; ArgArray2...: Integer); // Any call to this function will be ambiguous
Function F (ArgArray1...: Integer "Separator" ArgArray2...: Integer); Function F (ArgArray1...: Integer; ArgArray2...: Double); // That's better.
Function G (ArgArray...: Integer; X: Integer(0));
G 10; // Ambiguous: does the 10 go in ArgArray or in X? G 10, ; // That's better; the missing argument has to be X
If the types of parameters to an overloaded function match one of the functions exactly, that function is called. If not, a scoring system must be used to determine which of the functions to call. For example,
Function Main: X: UInt16(10); F X; End;
Function F (X: Int32); Function F (X: Int16);
In this case, the scoring system dictates that the F taking Int32 is called. It is important to realize that the scoring system is used only when
* to be completed *
You may declare a function before defining it. For example:
Function MyFunc (X: Integer(1)): Integer;
Function MyFunc (X: Integer(1)): Integer
{
Return X * 10;
}
Normally, this is not necessary, bacause QDL is a two-pass compiler: on the first pass, declaration information (such as information about variables and functions) is gathered, and on the second pass, definitions are compiled. However, there are still cases where it is necessary to declare a function without defining it. In particular:
When a function is declared or defined, the compiler searches to find out whether the signature has already been declared. If it has, the following rules apply:
For example:
Static Inline Function F (X, Y: Integer(5)): Integer(10); // First declaration Static Inline Function F (X: Integer(5); Y: Integer(5)): Integer(10); /* OK. Although the X and Y are separated, the signature is the same. */ Static Inline Function F (X, Y: Integer(4)): Integer(1); /* Error. Default arguments have changed */ Function F (X, Y: Integer(5)): Integer(10); /* Error. The same function type modifiers must be present. */ Static Inline Function F (X, Y: Integer(5)): Integer; /* Error. Default return value must be respecified. */ Static Inline Function F (X, Y: Const Integer(5)): Integer(10); /* Error. Const status must remain the same */
Functions that are to be members of a class may not be declared or redeclared outside a Class statement, only defined.
QDL functions can be nested. To do this, simply put one inside the other:
Function SaveFile (FileName: String) Returns Boolean(True):
File: IOStream;
Function OpenFile():
File.Open FileName For Output, Text;
End;
Function WriteData():
Function Phase1():
File.Print "Hello\n";
End;
Function Phase2():
File.Print "Goodbye\n";
End;
Phase1, Phase2;
End;
Function CloseFile():
File.Close;
End;
Try {
OpenFile
WriteData;
} Catch (E: EOpenFailure)
Return False;
CloseFile;
}
It goes without saying, for anyone who's used a language that supports this, that code in the sub-functions is not executed until you call the function from the body of the parent function. Rather like Pascal, you must put the sub-functions at the top of the parent function. The only thing that can be above the function definitions is var-statements, and non-executable statements such as Class and Enum. Variables declared above the functions, including arguments to the parent function, are accessible by sub-functions, while variables below are not.
QDL allows you to do more than simply call a sub-function from the parent function.
There is a danger associated with this case. The sub-function can only access the variables of the parent function as long as the parent function is suspended somewhere on the call stack. Some kind of error will occur if the parent function returns, and then the sub-function is called.
All sub-functions are implicitly Static; however, a sub-function has a This reference if the parent does.
The implementation I am thinking of for this feature is not multithread-safe. It involves using a hidden static variable to point to the location of the parent function's variables on the stack. When the parent function is entered, the value of this variable is saved on the stack and changed to point to the variables. Thus, whenever the child functions are called, they have access to the parent's variables through this pointer. When the parent function returns, it restores the hidden variable's value from the stack.
It is not multithread-safe bacause, if two copies of the function are running, the copy that was called first will be set to access the second copy's variables (or something worse, if the threads use two different stacks that share the same address space.) Also, the hidden variable will be set wrong if the functions do not return in the reverse order they were called. I've heard, though, that it is possible to have thread-local "global" variables, which would solve that problem. There may be significant overhead, however, for a function to use thread-specific storage.
In later chapters, Enum and With will be described in detail. For now, I only mention that enumerations get special treatment when used as function arguments. Take a look at the following enum and function declaration:
Enum KissModes { Lips, Ass };
Function KissMy (MyWhat: KissModes);
Suppose that this Function and Enum is located in a class called HelloDude, of which you have an instance called HeyLady. If this were the case in C++, you would have to call the function like this:
HeyLady.KissMy (HelloDude::Lips);
In QDL, however, the scope resolution, HelloDude::, is unneccessary. This is because, when interpreting the argument, the compiler acts like there's a With HelloDude::KissModes surrounding the argument (mind you, it's not syntactically correct to put a With block inside another statement, which is why I used a simile.) Thus, you can call the function like this instead:
If SheLovesMe Then HeyLady.KissMy Lips; Else HeyLady.KissMy Ass;
Of course, you'd never write statements like these, but QDL puts this feature to good use in a lot of the standard library, and even in the built-in classes.
| Table of Contents | Qwertie's Site/Mirror |