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

Executable Statements Vol. 1
Divider

Executable statements are statements that can be used only inside the block of a Function statement. There are 17 of them. But before I get to explaining them, I should introduce labels. Labels are not executable, but they are found only where an executable statement is expected. Their syntax is:

label: label-identifier:

In other words, an identifier followed by a colon. Label names do not occupy the same namespace as functions and variables because their names are only used in particular contexts. Labels may only be located at the beginning of the following statement types: Do-While, Do-Until, While, Until, Switch, For. The scope of a label is the loop in which it is located; labels do not enable you to jump to a different function or even a different loop.  A label is considered part of the statement it precedes.

In the context of this section and the next section, executable-statement means either:

When an executable-statement is expected, any number of Compile/preprocessor statements may legally be found, without alleviating the need for an executable-statement. For example:

If MyBooleanVariable Then
	Compile MamboNumber "5";
	MyFunction;

Because the first statement is a Compile statement, the code is legal, but the Compile statement is not considered to be the content of the required executable-statement. Thus, the next line, MyFunction;, fits the bill instead and will be executed only if MyBooleanVariable is True.

Non-Compile statements that not executable are illegal in the same context. For example, the following produces an error:

If MyBooleanVariable Then     // To fix the error, surround the statements in { }
	X, Y: Integer(0); // Error
	MyFunction X, Y;

Function calls as statements

A function call statement is identified by an expression at the beginning of the statement which:

See here for more information and examples.

Expressions as statements

expression-statement: expression;

For the compiler to identify a statement as being an expression-statement, the following criteria must be met:

Note: A semicolon by itself (;) is considered to be an executable expression-statement, albeit one that does nothing.

The Return statement

return-statement: Return [expression];

The return statement exits a function, and, for functions that have a return type, returns a value to its caller. For those functions that do not have a return type, the expression must be omitted. For those functions that do have a return type:

A return statement can be located inside any number of sub-blocks within the function.

For functions that have a return type but no default return value, one of the following must be true concerning the block that contains the function's executable statements:

  1. A Return statement is the last executable statement in the block
  2. An If/Unless statement is the last executable statement in the block, and the executable statement(s) within the If/Unless statement conform recursively to these rules.

The With statement

with-statement: With (class-lvalue-expression | member-scope) Do statement

The main intent of the With statement is to save typing; no programming task actually requires the use of such a statement. Between With and Do is one of the following, which identifies the "With type":

  1. An expression that evaluates to an lvalue of a Class type (class-lvalue-expression).
  2. The name of a Class or Enum type accessible from the current scope (member-scope). Naming the type may require the use of the scope-resolution operator, ::, as later described.

If the type of the With block is ambiguous, the type is assumed to be (1). For example, if you have a class called X and a variable called X, the following:

With X Do

Is assumed to initiate a type (1) with statement using X as the class-lvalue-expression.

With Type (1)

Inside a type (1) With statement, access to the lvalue between the With and the Do is implied, in the same way that members of a class can be accessed implicitly without the use of the This keyword.  For example, if you have:

...and you want to access the members of ClassVar several times, you could use a With statement:

With MyClassInstance Do {
	X += A[0];
	F X;
}

Without using the With statement, the functionally equivalent code would be:

MyClassInstance.X += MyClassInstance.A[0];
MyClassInstance.F MyClassInstance.X;

Note that if you had a variable called X, access to it would be blocked within the With block by the member called X in MyClass

When you use a With statement, the expression you specify is not simply expanded when a member needs to be accessed. Rather, a hidden reference is kept to the lvalue, so that even if the value of the original expression changes somewhere in the With block, expressions still access the same address. Using the same Class and set of variables as in the examples above,

MyClassRef: @ MyClass (MyClassInstance);
With MyClassRef Do {
	@MyClassRef = Null;
	X += A[0];
	F X;	
}

Even though the pointer in MyClassRef is changed, the following statements still access MyClassInstance, because of the hidden reference that has been stored pointing to it.

If the object that is the subject of a With statement is Const, then read-only access is provided to the object.

Contents of a type (1) With block are also subject to the abilities afforded to a type (2) block, with the Class of the class-lvalue-expression used as the member-scope.

With Type (2)

In a type (2) With statement, a type is specified between the With and Do, not an instance of a type. For example, if you have a class called *Foo* containing an Enum called <Bar>, you might write this to start your With statement:

With *Foo*::<Bar> Do

When the type specified is an Enum, the identifiers that make up the Enum are available without qualification within the With block. If the type specified is a Class, then:

A function, function reference, or other variable made accessible by a With block blocks all overloaded functions in the outer scopes, not just those matching the signature of the identifier newly made available.

Nested With Blocks

Sub-blocks can be placed inside With blocks, including other With blocks. Statement(s) inside the inner With block can access variables and functions from both scopes, as long as names don't conflict. If they do, only the subject of the inner With block is accessible. For example, if you have two Classes and a self-named instance of each:

Then, in the following code segment:

With Class1 {
	X = Y();
	With Class2
		Y = Z();
}

In the outer block, Y refers to the function in Class1. In the inner block, Y refers to the variable in Class2, and blocks access to the function in Class1, even if you include the brackets (Y()) Of course, you can always use Class1 directly: Class1.Y().

The If and Unless statements

if-statement: If boolean-expression Then executable-statement [Else executable-statement]

unless-statement: Unless boolean-expression Do executable-statement [Else executable-statement]

The If statement conditionally executes the statement following Then if (and only if) the Boolean expression is True. The Unless statement conditionally executes the statement following Do if (and only if) the Boolean expression is False.

When an Else clause is provided in either statement, the statement following Else is executed if the first executable-statement could not be executed.

As with anywhere where an expression of a certain type is expected, the compiler will attempt to implicitly cast the expression to the desired type (Boolean, in this case) if that data type is not the result of the expression.

Unless usage example:

Unless Math.Random (1 To 10) == 1 Do
	StdOut.PrintLine "Sorry, try again.";
Else
	StdOut.PrintLine "You're pretty lucky today.";

The Switch statement

switch-statement: [label] Switch pivot-expression { switch-case* [default-case] }
switch-case: Case <expression>: statement*
default-case: Case Else: statement*

The QDL Switch statement determines the value of pivot-expression, then compares it to each of the Case expressions by invoking the == operator of the type of the value against each Case expression. When a matching value is found to match, the block of code that follows it is executed. If the value matches none of the Case expressions, the code for the default case (Case Else) is executed. The Case expressions do not have to be constant, nor do they have to evaluate to a built-in type.

Once a matching Case is found, no more Case expressions are tested against the pivot value. Having duplicate Case values is therefore pointless. It may not be possible for the compiler to determine that two values are equal; however, if the values in question are constants, the compiler must be able to notice duplicates and issue a compile-time error.

The Switch statement functions similarly to a set of If-Then-Else statements, where T is the type of pivot-expression:

Temp: T;
Temp = (pivot-expression);
If Temp == (case1-expression1) || Temp == (case1-expression2) ... Then {
	case1-statements
} Else If Temp == (case2-expression1) || Temp == (case2-expression2) ... Then {
	case2-statements
} Else {
	case-else-statements
}

The best way to understand this statement is through an example:

Name, FirstName: String;
StdIn.Input Line Name;
Name = Name.LTrim.LCase;     // Trim spaces at beginning and convert to lowercase.
If Name.Instr(" ") >= 0 Then // If full name was put in, use only first name
	FirstName = Name.Left (Name.Instr(" "));
Else
	FirstName = Name;
Switch FirstName {
Case "mario", "luigi":
	StdOut.Print Line "Mama Mia!";
Case "fred", "barney":
	StdOut.Print Line "Yabbadabbadoo!";
Case "stan", "kyle", "kenny", "eric":
	StdOut.Print Line "Didn't know you guys used computers!";
Case "david":
	StdOut.Print Line "All hail!";
Case Else:
	StdOut.Print Line "I know ye not, go thither."
}

C Programmers Beware

Note that, unlike C/C++, execution does not "fall through" from one case to the next, which is why each case can have multiple values. On occasion, in C you would want to do something like this:

switch (N) {
case 1:
	// Stuff to be done if N==1 goes here
	// No break here!
case 2:
	// Stuff to be done if N==1 or N==2 goes here
	break;
}

You can still accomplish this in QDL, using this technique:

Switch N {
Case 1:
	// Stuff to be done if N==1 goes here
	N = 2;
	Continue;
Case 2:
	// Stuff to be done if N==1 or N==2 goes here
}

In addition to Break, the QDL Continue statement applies to Switches, as does the Redo statement. Thus, Continue re-executes the Switch statement, re-evaluating N and each of the cases, and Redo re-executes the Case section currently executing.

If you want to use Continue (or, for that matter, Break or Redo) to jump to the beginning of the loop enclosing the Switch, you must use a label. For example:

OuterLoop: For N = 1; N <= 10; N++ Do {
	Switch (N) {
	Case 1:
		// Stuff to be done
		Continue OuterLoop;
	// ...
	}
}
Table of Contents Qwertie's Site/Mirror
Next
Previous