Free Web Hosting by Netfirms
Web Hosting by Netfirms | Free Domain Names by Netfirms

Compile, Uses, and Namespace
Divider

Compile-series statements

Compile statements are non-executable statements that

Compile statements implement functionality nearly identical to that of C preprocessor statements. In fact, QDL supports certain C-style preprocessor statements, all of which have a QDL-style equivalent through the use of a Compile statement.  

Compile statements look similar to normal QDL statements, and are free-form. C-style preprocessor statements always begin with a # character at the beginning of a line, and end at the end of the same line, unless the line ends with the backslash (\) character. The C-style statements have the advantage that they can be used anywhere, even inside another statement. Compile statements, on the other hand, can only be used where a statement is expected to begin.

The following table lists the C-style preprocessor statements QDL supports and their QDL equivalents:

C-style QDL equivalent C-style QDL equivalent
#define Compile Define ...; #endif Compile EndIf;
#include Compile ...; #else Compile Else:
#if ... Compile If ...: #elif ... Compile ElseIf ...
#ifdef ... Compile If Defined(...): #error Compile Error ...;
#ifndef ... Compile Unless Defined(...): #warning Compile Warning ...;

Any line that starts with #, but which is not followed by pragma and does not match one of the entries in the table above, causes an error to be issued.  The C-style #pragma statement is not supported in QDL. C-style #pragma statements are always ignored.

The following sections mainly emphasize the QDL forms of these statements.

You may be wondering why it always takes more typing to do something with the QDL preprocessor than the C one.  It is because the Compile series of statements is intended for rare use in special circumstances.  Statement length doesn't matter for features that are rarely used, so I thought I'd save some keywords by using only one (Compile), where the word that follows is merely a parameter, not classified as a keyword.  The most commonly used preprocessor statement in C (#define) is superceded in QDL by Const variables.

Conditional Compilation Directives

Conditional Compilation Directives are preprocessor statements that control whether other statements are compiled. These statements surround blocks of code that you do or do not want to be compiled depending on certain conditions.

The syntax is:

if-compile: Compile (If | Unless) const-expression:
elseif-compile: Compile Else (If | Unless) const-expression:
else-compile: Compile Else:
endif-compile: Compile EndIf;

conditional-compile-block: if-compile [code] [elseif-compile [code]]* [else-compile [code]] endif-compile

Where const-expression is an expression that evaluates to a constant and which can be converted to Boolean, in order to determine whether to compile the following code segment. When const-expression contains macros, they are expanded as they would be in normal statements.

When you using the form of the Compile statement that uses If or Else If, the const-expression must be True to compile the following code; when using Unless or Else Unless, the const-expression must be False to compile the following code.

Here is a simple example:

	S: String;
Compile If Defined (_DEBUG):
	S = "";
Compile Else:
	S = "not ";
Compile EndIf;
	StdOut.Print "This program is ", S, "being compiled in debug mode";

When it is determined that a certain block of code will not be compiled, the compiler still has to scan the code to notice Compile statements. It must keep track of nested Compile statements and notice the beginning and end of comments and string literals so that things inside them do not get mistaken for Compile statements. It does not, however, have to pay attention to or expand macro definitions. For example:

Compile If Defined(_Debug):             // Line 1
	... " ... Compile ... " ...     // Line 2
	Compile If ...:                 // Line 3
		/* ... Compile ... */   // Line 4
		...                     // Line 5
	Compile EndIf;                  // Line 6
Compile Else:                           // Line 7
	...                             // Line 8
Compile EndIf;                          // Line 9

If _Debug is not defined, the compiler still must keep track of where comments begin and end, and where string literals begin and end, so that it doesn't interpret what is in those strings and statements. It also must keep track of nested Compile statements, so that the Compile EndIf on line 6 (instead of line 9) is not interpreted as ending the Compile If started on line 1.

You can also use C-style conditional directives; here's an example:

#ifdef _QDL
	// QDL-specific code
#elif defined(__CPLUSPLUS__)
	// C++ specific code
#endif

Use of C-style directives is not recommended unless you need to include a file in both a C and a QDL program.  You can mix C-style and native conditional compilation directives. (But why would you want to?)

Macros

A macro is an identifier that, when found in the code following its location of definition (except within string literals and within comments), is replaced with another set of characters for the purposes of compilation.

Use the Compile Define statement to define or un-define a macro. This is its syntax:

define-statement: Compile Define macro-identifier [[(arguments)] const-string-expression | Null];
arguments: arg-identifier [, arg-identifier]*

macro-identifier may be anything that follows the identifier-naming rules, including reserved words, but excluding the following: Compile, Define, If, Unless, Else, EndIf, Null.

Example use:

Compile Define Sheep "10";
StdOut.Print "Counting sheep! "
For X: Integer(1); X <= Sheep; X++ Do
	StdOut.Print X, " ";
StdOut.Print "ZZzzz...";
// Output: Counting sheep! 1 2 3 4 5 6 7 8 9 10 ZZzzz...

The macro is defined using a string expression so that any characters you want can be put in the macro. For example, this C-style macro:

#define EXAMPLE !(%;$"3

Cannot be conveyed using a free-form statement without delimiting the expansion string:

Compile Define EXAMPLE "!(%;$\"3";

Of course, the quotes are removed when the macro is expanded. Mind you, if you type:

Compile Define Sheep 10

It is still legal because 10 can be converted to a string, "10".

Macros are expanded any time the identifier is found within the macro's scope, not just in ordinary expressions. If Sheep was found where a type name was expected, for example, it would still be expanded to 10.

After the string is evaluated, occurances of other macros in the string are expanded. For example:

Compile Define Macro1 "100";        // Line 1
Compile Define Macro2 "Macro1";     // Line 2
Compile Define Macro3 "Macro2";     // Line 3

When line 2 is compiled, the compiler finds that Macro1 is already defined and replaces it with 100, so Macro2 is defined as 1    00. Likewise, when line 3 is defined, it finds that Macro2 is defined as 100, so Macro3 ends up being defined as 100 also.

Macro replacement only occurs once. For example:

Compile Define Macro1 "Macro2 + Macro3";   // Line 1
Compile Define Macro2 "Macro1 + Macro2";   // Line 2
Compile Define Macro3 "100";               // Line 3
Compile Define Stringize (X) "#X";         // Line 4
                                    // Line 5
StdOut.Print Stringize (Macro2);    // Line 6

Because Macro2 and Macro3 are not defined when line 1 is compiled, Macro1 gets defined as Macro2 + Macro3. When line 2 is compiled, Macro1 is expanded, but Macro2 itself is not. Thus, the resultant definition is Macro2 + Macro3 + Macro2. The expansion of a macro when used is not affected by definitions of macros defined afterward. In this example, when Macro2 is used in line 6, it is not affected by the definition of Macro3 in line 3. Therefore, the output of line 6 is: Macro2 + Macro3 + Macro2.

The Stringize() macro illustrates two things: function-style macros and stringization. First of all, when using a function-style macro, the brackets are always required, even if you're just using a macro on a line by itself. That's because the macro-replacement feature is performed on a low level (it's all part of the idea that macro replacement is a pre-processor feature, see.) Secondly, QDL supports the same stringization and concatenation features as C. When a single # character is found in the macro string, the token following it is expanded into a string literal. Thus, Stringize (Macro2) on line 6 is expanded to "Macro2 + Macro3 + Macro2". The following macro definition:

Compile Define Stringize (X) "\"X\"";

Would not have worked, because when X, or another macro, is found inside a string literal like this, it is never expanded. If you had written the macro like this, line 6 would simply output X.

Concatenation is also supported, though I haven't found a use for it so far. ## is the concatenation operator, and like stringization, it only works within macro definitions. Here is are a couple of examples:

Compile Define Concat1 "Hey, ## man ## you're cool";
Compile Define Concat2(X, Y) "X ## Y";

The first example ends up being defined as Hey,manyou're cool. The second example's result depends on the value of X and Y. If you use, for instance, Concat2 (1 + 1, 2 + 2), it will be replaced with 1 + 12 + 2.

Once a macro is defined, it can be undefined using the Define statement with Null instead of an expansion string.  For example:

Compile Define Macro1 Null;

This removes preprocessor meaning from Macro1.  Attempting to undefine an identifier that was not previously defined is legal, though it has no effect, and a warning may be issued.

Predefined Macros

Several macros are always predefined in QDL.  These are as follows:

__File__, _File_, __Line__, and _Line_ are special macros because their value constantly changes (as the current file and line numbers change). Another special property they have is that they are not expanded when they appear in other macros. For instance,

Uses "Streams\Console";          // Line 1
Compile Define MyDef "__Line__"; // Line 2
                                 // Line 3
Function Main:                   // Line 4
	StdOut.Print MyDef;      // Line 5
End;

The output in this case is 5, not 2.

None of these macros can be undefined or redefined.

User-defined compilation messages

The following forms of Compile can be used to output warning, error and notice messages during compilation.

Compile Error const-string-expression;
Compile Warning const-string-expression;
Compile Message const-string-expression;

Error messages generated with Compile Error are fatal, as are Compile Warning messages if the compiler is set to treat warnings as errors.

Compile Option

A Compile Option statement is a means for a compiler to provide a feature that is not part of the basic QDL language, or override compiler options. Its syntax is

Compile Option option-identifier text;

Where option-identifier is a name that is significant to the target compiler and text is a set of tokens to be interpreted by the target compiler.

A compiler should ignore, or issue a warning for, a Compile Option statement when it doesn't recognize the option-identifier. However, pragma must be parsed because it can contain sub-blocks, comments and string literals in which semicolons are not treated as the end of the statement. For example,

Compile Option blablabla starting a block... {
	Inside this block semicolons (;) do not end the pragma.
	Furthermore, you can have subblocks: { like this };
	And now for a comment /* } ; */
} And now for a string literal: ";".
Okay, we're done;

Only the very last semicolon ends the statement. The others do not due to being in comments/string literals/blocks. The compiler must also keep track of block nesting, and ignore braces in comments or string literals.

My implementation of the compiler supports the following pragmas:

option-
identifier
text syntax Description
Pack (1 | 2 | 4 | 8 | 16 | Default | Pop) Sets the data alignment mode of Classes that follow the option.
  • 1: Pack tightly (one-byte alignment)
  • 2: Aligns objects containing 16-bit numbers on 2-byte boundaries.
  • 4: Same as 2, except that objects containing 32-bit numbers (integer or floating-point) are aligned on 4-byte boundaries.
  • 8: Same as 4, except that objects containing 64-bit numbers (integer or floating-point) are aligned on 4-byte boundaries.
  • Default: Use the compiler's default value.
  • Pop: Whenever the pragma is used with one of the other values, the value is added to an internal compiler stack. Use Pop to remove the last value from the stack and use the previous packing value.

    For example, you can change the pack setting for a Class and restore the original value afterward using something like this:

    Compile Option Pack 1;
    Class MyClass:
    	...
    End Class;
    Compile Option Pack Pop;
TraceInto (On | Off) Controls whether the debugger should trace through the functions that follow.  Trace defaults to On.  It is useful to turn off tracing for functions you know work properly, so you can focus on more questionable functions.  For example, standard library functions should have tracing turned off.  Turning tracing off does not mean debug information is removed; a debugger should include a force-trace option that allows functions with this option Off to be traced into.

Including other files 

include-compile-statement: Compile "filename" [, "filename"]*;
uses-statement: Uses [Standard | Explicit]* <uses-component>;
uses-component: "filename" [{ translation-options }]

The filenames specified in the Compile and Uses statements are not string literals in the sense used in other parts of QDL. That's because there are no escape sequences allowed in the filename; backslash characters are not treated as the beginning of an escape sequence, but rather a path separator. If an absolute path is specified, the compiler will look in that path and no other.  However, filename doesn't have to be a complete path to the file you want to open.  If a relative path is specified, the relative path is added to each of the standard paths and file searching occurs in those paths.

The standard paths, which are searched in the order listed, are:

  1. The directory that contains the source file that contains the Compile/Uses statement.
  2. Project-specific include directories
  3. Standard include directories

The presence of the word Standard beside Uses causes searching for all files listed in the statement to be short-circuited to path #3.

filename can be either a file name or a directory name.  If filename is identified as a directory, then all the .h and .qdl files in the directory and all its subdirectories are scanned.  

If an extension is not specified, the compiler will attempt to open the file first with the .h extension, then with the .qdl extension, and finally with no extension.

A file or directory name may be located in more than one of the standard paths.  In this case, the first path that matches is used and no other.  For example, if "Files" is found in #2, #3 is not searched.

QDL pro vides two ways to include files.

  1. With the include-compile-statement.  When using this statement, each of the files whose names are specified are opened and interpreted as if they were part of the current file.
  2. With the Uses statement.  When using this statement, each of the files whose names are specified are opened and interpreted only for declarations. That is, the file is compiled fully, but the code is treated differently.  Any given file compiled through Uses is interpreted only once by the compiler.  For example, Uses "A", "A" repeated ten times will have the same effect as Uses "A" once.  The Uses statement is similar in purpose to the Pascal uses statement, which causes the specified module's interface to be scanned and not the implementation section. The include-compile-statement, meanwhile, functions similarly to the C #include directive.

Compile may be used anywhere a statement is expected; Uses can only be used at global scope.  Uses in a file A interprets a file B in the following ways:

The translation-options allow you to alter or hide the names used in B from the perspective of A.  This section consists of zero or more of the meta-statements (special statements that have meaning only within a translation-options clause) Rename and Private.

translation-options: [rename-metastatement | hide-metastatement]*
rename-metastatement: Rename (old-resolved-identifier As new-identifier | [Class] old-resolved-typeidentifier As new-resolved-typeidentifier);
private-metastatement: Private access-name-spec [, access-name-spec]*;
access-name-spec: (resolved-identifier | [Class] resolved-typeidentifier)

Note: If As is, or is part of, the name of the identifier to be renamed, the name may be enclosed in brackets to avoid confusing the compiler.

Rename changes the name of an identifier.  The namespace associated with the new identifier is the same as that of the old identifier.  The old identifier is interpreted only in the context of the file(s) that were just scanned, so name resolution is not necessary if the identifier conflicts with an externally defined identifier.  For example:

// A.QDL
Class MyClass:
End Class;
// B.QDL
Class MyClass:
End Class;
// C.QDL
Uses "A", "B" {                       // Line 2
	Rename MyClass As MyClassOfB; // Line 3
};                                    // Line 4

In this case, the Rename meta-statement on line 3 unambiguously renames B::MyClass, since A::MyClass is not accessible within the block.

Private removes the ability to implicitly access an identifier.  In other words, access to the identifier can be achieved only if its namespace is specified explicitly.  Private does not fully block access to the identifier.

The Namespace Statement

namespace-statement: Namespace namespace-spec [, namespace-spec]*:
namespace-spec: ([::]typeidentifier[::typeidentifier]* | Null | Private)

This directive sets the namespace in which to place the symbols that follow.  Namespaces are used to uniquely identify a set of functions, variables and classes, in order to resolve name conflicts.  The name of a namespace must follow the same rules as the names for classes.  The Namespace statement may be placed anywhere in the file as long as it is at global scope, and only affects declarations that follow it.  There may also be multiple Namespace statements in a file.  As the syntax implies, you may specify multiple namespace paths for the same symbols.  The first namespace-spec specifies the actual namespace in which to place the symbols; those that follow are aliases, rather like link files on Linux systems.

By default, the namespace name of a module is derived from its filename by removing the extension and all characters and character sequences that are illegal in type names.  This namespace is placed inside the namespace called Default.  Some examples:

In a Uses statement in file A, the included files do not inherit the namespace from A, but rather have their own.  A Compile include statement, however, treats the included file as if it were actually expanded in A, so the namespace is inherited from A.

It is recommended that you use namespaces with two levels (separated with ::) according to the following convention:

To give some idea how namespaces work, check out the following files:

// A.QDL
Namespace Microsloth::Guitar Works:
. . .
Class Guitar:      End Class;
Class Bass Guitar: End Class;
// B.QDL
Namespace Acoustic Masters::Guitar Library:
. . .
Class Guitar:          End Class;
Class Acoustic Guitar: End Class;
// C.QDL
// Default namespace is Default::C
Uses "A", "B";
V: Bass Guitar;        // Okay, there is only one Bass Guitar defined
W: Guitar;             // Error, there are two Guitars defined
X: Microsloth::Guitar Works::Guitar;   // Okay, conflict resolved
Y: Guitar Library::Guitar;             // Also Okay
Class Guitar:    End Class;  // Error.  Name is defined already; resolution required.
Class C::Guitar: End Class;  // Okay
Class D::Guitar: End Class;  // Error.  We are in the "C" namespace, not "D".
Q: Guitar;        // Still an error.  C::Guitar is NOT chosen by default.

Function F:
	Class ::C::MyClass: End Class; // Error, namespace can only be specified
	// for a class at global scope; otherwise the namespace is implied.
End;

Namespaces prevent the two Guitar classes from conflicting with one another at the point of the Uses statement; being in different namespaces makes them distinct entities as far as the compiler is concerned.

As you can see by the line

Y: Guitar Library::Guitar;

The complete namespace path doesn't have to be specified: only the inner path has to be specified, provided that the outer path is not required to resolve the conflict.  In the example above, if both packages had been called "Guitar Library", the conflict would remain, so the outer path would have to be specified as well.

You may wonder why this line in C is an error:

Class Guitar:    End Class;

You might say, Guitar is in namespace C by default so the programmer should not have to specify that it is.  It is set up this way in order to force the programmer to realize (s)he is duplicating a name that has already been used.

When performing class derivation, the base class(es) do not have to be in the same namespace, nor does the derived class inherit the namespace of the base class(es).  If namespace resolution was required to specify which base class to use, namespace resolution will still be required to access the base class anywhere in the class's definition.  For example:

Class Pretty Guitar Inherits Guitar Library::Guitar {
	Function F {
		Var G: Guitar; // Error, resolution still required
		Var G: Guitar Library::Guitar; // Okay
	};
};

Different modules in a program may use the same namespace.

A class and a namespace may have the same name.  In this case, the compiler will prefer to interpret the use of the name as referring to the class.  The unary scope resolution (aka global) operator changes this interpretation; when used, it will favor seeing the identifier as a namespace name.  For example:

Namespace X:
Class Y:
End Class;
Class X:
End Class;
YVar: X::Y;   // Error: Class X does not contain a class Y
YVar: ::X::Y; // Okay: Compiler sees namespace X, class Y

The two keywords Null and Private have special meaning.  The Null keyword indicates that the declarations are in the global namespace (::), also known as the null namespace.  If the compiler supports direct interfacing with C modules, and possibly other languages, you must place the declarations that need to be shared between C and QDL in the Null namespace; otherwise, it is recommended that declarations not be placed in the global namespace. The Private keyword indicates fairly the opposite: that the declarations that follow are private to the module and are not accessible to any other module. 

Table of Contents Qwertie's Site/Mirror
Next
Previous