www.digitalmars.com [Home] [Search] [D]
Last modified Dec 7, 2001.

Statements

C and C++ programmers will find the D statements very familiar, with a few interesting additions.
	Statement:
		EmptyStatement
		LabeledStatement
		BlockStatement
		ExpressionStatement
		DeclarationStatement
		IfStatement
		DebugStatement
		VersionStatement
		WhileStatement
		DoWhileStatement
		ForStatement
		SwitchStatement
		CaseStatement
		DefaultStatement
		ContinueStatement
		BreakStatement
		ReturnStatement
		GotoStatement
		WithStatement
		SynchronizeStatement
		TryStatement
		ThrowStatement
		AsmStatement

Empty Statement

Empty statements are placeholders in the syntax, they don't do anything.
	EmptyStatement:
		'{' '}'
	
The empty statement in C is represented by a solitary ;. This can lead to a difficult to see error:
		while ((I = func()) != 0);
			test();
	
Note the extraneous and nearly invisible ; after the while(). D solves this by requiring that an empty statement be represented by explicit { }. For example, a loop with an empty body is written:
	for (i = 0; i < func(); i++)
		{}
	

Labelled Statements

Statements can be labelled. A label is an identifier that precedes a statement.
	LabelledStatement:
		Identifier ':' Statement
	
Any statement can be labelled, including empty statements, and so can serve as the target of a goto statement. Labelled statements can also serve as the target of a break or continue statement.

Labels are in a name space independent of declarations, variables, types, etc. Even so, labels cannot have the same name as local declarations. The label name space is the body of the function they appear in. Label name spaces do not nest, i.e. a label inside a block statement is accessible from outside that block.


Block Statement

A block statement is a sequence of statements enclosed by { }. The statements are executed in lexical order.
	BlockStatement:
		{ }
		{ StatementList }

	StatementList:
		Statement
		Statement StatementList
	
A block statement introduces a new scope for local variables.

Expression Statement

The expression is evaluated.
	ExpressionStatement:
		Expression ;
	
Expressions that have no affect, like (x + x), are illegal in expression statements.

Declaration Statement

Declaration statements declare and initialize variables.
	DeclarationStatement:
		Type IdentifierList ;

	IdentifierList:
		Variable
		Variable , IdentifierList

	Variable:
		Identifier
		Identifier = Expression
	

If Statement

If statements provide simple conditional execution of statements.
	IfStatement:
		if ( Expression ) Statement
		if ( Expression ) Statement else Statement
	
Expression is evaluated and must have a type that can be converted to a boolean. If it's true the if statement is transferred to, else the else statement is transferred to.

The 'dangling else' parsing problem is solved by associating the else with the nearest if statement.


Debug Statement

Two versions of programs are commonly built, a release build and a debug build. The debug build commonly includes extra error checking code, test harnesses, pretty-printing code, etc. The debug statement conditionally compiles in its statement body. It is D's way of what in C is done with #ifdef DEBUG / #endif pairs.
	DebugStatement:
		debug Statement
		debug ( Integer ) Statement
		debug ( Identifier ) Statement
	
Debug statements are compiled in when the -debug switch is thrown on the compiler.

debug(Integer) statements are compiled in when the debug level n set by the -debug(n) switch is <= Integer.

debug(Identifier) statements are compiled in when the debug identifier set by the -debug(identifer) matches Identifier.

If Statement is a block statement, it does not introduce a new scope. For example:

	int k;
	debug
	{   int i;
	    int k;	// error, k already defined

	    i = 3;
	}
	x = i;		// uses the i declared above
	
There is no else clause for a debug statement, as debug statements should add code, not subtract code.

Version Statement

It is commonplace to conveniently support multiple versions of a module with a single source file. While the D way is to isolate all versioning into separate modules, that can get burdensome if it's just simple line change, or if the entire program would otherwise fit into one module.

	VersionStatement:
		VersionPredicate Statement
		VersionPredicate Statement else Statement

	VersionPredicate
		version ( Integer )
		version ( Identifier )
	
The version statement conditionally compiles in its statement body based on the version specified by the Integer of Identifier. Both forms are set by the -version switch to the compiler. If Statement is a block statement, it does not introduce a new scope. For example:
	int k;
	version (Demo)	// compile in this code block for the demo version
	{   int i;
	    int k;	// error, k already defined

	    i = 3;
	}
	x = i;		// uses the i declared above
	
The version statement works together with the version attribute for declarations.

Version statements can nest.

The optional else clause gets conditionally compiled in if the version predicate is false:

	version (X86)
	{
	    ... // implement custom inline assembler version
	}
	else
	{
	    ... // use default, but slow, version
	}
	
While the debug and version statements superficially behave the same, they are intended for very different purposes. Debug statements are for adding debug code that is removed for the release version. Version statements are to aid in portability and multiple release versions. See also the Version Specification and Version Attribute.

Predefined Versions

Several environmental version identifiers and identifier name spaces are predefined to encourage consistent usage. Version identifiers do not conflict with other identifiers in the code, they are in a separate name space.
DigitalMars
Digital Mars is the compiler vendor
X86
Intel and AMD 32 bit processors
Win32
Microsoft 32 bit Windows systems
linux
All linux systems
LittleEndian
Byte order, least significant first
BigEndian
Byte order, most significant first
Others will be added as they make sense and new implementations appear.

It is inevitable that the D language will evolve over time. Therefore, the version identifier namespace beginning with "D_" is reserved for identifiers indicating D language specification or new feature conformance.

Compiler vendor specific versions can be predefined if the trademarked vendor identifier prefixes it, as in:

	version(DigitalMars_funky_extension)
	{
	    ...
	}
	
It is important to use the right version identifier for the right purpose. For example, use the vendor identifier when using a vendor specific feature. Use the operating system identifier when using an operating system specific feature, etc.

While Statement

While statements implement simple loops.
	WhileStatement:
		while ( Expression ) Statement
	
Expression is evaluated and must have a type that can be converted to a boolean. If it's true the statement is executed. After the statement is executed, the Expression is evaluated again, and if true the statement is executed again. This continues until the Expression evaluates to false.

A break statement will exit the loop. A continue statement will transfer directly to evaluationg Expression again.


Do-While Statement

Do-While statements implement simple loops.
	DoStatement:
		do Statement  while ( Expression )
	
Statement is executed. Then Expression is evaluated and must have a type that can be converted to a boolean. If it's true the loop is iterated again. This continues until the Expression evaluates to false.

A break statement will exit the loop. A continue statement will transfer directly to evaluationg Expression again.


For Statement

For statements implement loops with initialization, test, and increment clauses.
	ForStatement:
		for (Initialize; Test; Increment) Statement

	Initialize:
		empty
		Expression
		Declaration

	Test:
		empty
		Expression

	Increment:
		empty
		Expression
	
Initializer is executed. Test is evaluated and must have a type that can be converted to a boolean. If it's true the statement is executed. After the statement is executed, the Increment is executed. Then Test is evaluated again, and if true the statement is executed again. This continues until the Test evaluates to false.

A break statement will exit the loop. A continue statement will transfer directly to the Increment.

If Initializer declares a variable, that variable's scope extends through the end of Statement. For example:

	for (int i = 0; i < 10; i++)
		foo(i);
	
is equivalent to:
	{   int i;
	    for (i = 0; i < 10; i++)
		foo(i);
	}
	
Function bodies cannot be empty:
	for (int i = 0; i < 10; i++)
		;	// illegal
	
Use instead:
	for (int i = 0; i < 10; i++)
	{
	}
	
The Initializer may be omitted. Test may also be omitted, and if so, it is treated as if it evaluated to true.

Switch Statement

A switch statement goes to one of a collection of case statements depending on the value of the switch expression.
	SwitchStatement:
		switch ( Expression ) BlockStatement

	CaseStatement:
		case Expression : Statement

	DefaultStatement:
		default: Statement
	
Expression is evaluated. The result type T must be of integral type or char[] or wchar[]. The result is compared against each of the case expressions. If there is a match, the corresponding case statement is transferred to.

If none of the case expressions match, and there is a default statement, the default statement is transferred to.

If none of the case expressions match, and there is not a default statement, a SwitchException is thrown. The reason for this is to catch the common programming error of adding a new value to an enum, but failing to account for the extra value in switch statements.

The case expressions must all evaluate to a constant value or array, and be implicitly convertible to the type T of the switch Expression.

Case expressions must all evaluate to distinct values. There may not be two or more default statements.

Case statements and default statements associated with the switch can be nested within block statements; they do not have to be in the outermost block. For example, this is allowed:

		switch (i)
		{
		    case 1:
		    {
			case 2:
		    }
			break;
		}
	
Like in C and C++, case statements 'fall through' to subsequent case values. A break statement will exit the switch BlockStatement. For example:
		switch (i)
		{
		    case 1:
			x = 3;
		    case 2:
			x = 4;
			break;
		    case 3:
			x = 5;
			break;
		}
	
will set x to 4 if i is 1.

Note: Unlike C and C++, strings can be used in switch expressions. For example:

	char[] name;
	...
	switch (name)
	{
	    case "fred":
	    case "sally":
		...
	}
	
For applications like command line switch processing, this can lead to much more straightforward code, being clearer and less error prone. Both ascii and wchar strings are allowed.

Implementation Note: The compiler's code generator may assume that the case statements are sorted by frequency of use, with the most frequent appearing first and the least frequent last. Although this is irrelevant as far as program correctness is concerned, it is of performance interest.


Continue Statement

A continue aborts the current iteration of its enclosing loop statement, and starts the next iteration.
	ContinueStatement:
		continue;
		continue Identifier ;
	
continue executes the next iteration of its innermost enclosing while, for, or do loop. The increment clause is executed.

If continue is followed by Identifier, the Identifier must be the label of an enclosing while, for, or do loop, and the next iteration of that loop is executed. It is an error if there is no such statement.

Any intervening finally clauses are executed, and any intervening synchronization objects are released.

Note: If a finally clause executes a return, throw, or goto out of the finally clause, the continue target is never reached.


Break Statement

A break exits the enclosing statement.
	BreakStatement:
		break;
		break Identifier ;
	
break exits the innermost enclosing while, for, do, or switch statement, resuming execution at the statement following it.

If break is followed by Identifier, the Identifier must be the label of an enclosing while, for, do or switch statement, and that statement is exited. It is an error if there is no such statement.

Any intervening finally clauses are executed, and any intervening synchronization objects are released.

Note: If a finally clause executes a return, throw, or goto out of the finally clause, the break target is never reached.


Return Statement

A return exits the current function and supplies its return value.
	ReturnStatement:
		return;
		return Expression ;
	
Expression is required if the function specifies a return type that is not void. The Expression is implicitly converted to the function return type.

At least one return statement is required if the function specifies a return type that is not void.

Expression is illegal if the function specifies a void return type.

Before the function actually returns, any enclosing finally clauses are executed, and any enclosing synchronization objects are released.

The function will not return if any enclosing finally clause does a return, goto or throw that exits the finally clause.

If there is an out postcondition (see design by contract), that postcondition is executed after the Expression is evaluated and before the function actually returns.


Goto Statement

A goto transfers to the statement labelled with Identifier.
	GotoStatement:
		goto Identifier ;
	
Any intervening finally clauses are executed, along with releasing any intervening synchronization mutexes.

It is illegal for a goto to be used to skip initializations.


With Statement

The with statement is a way to simplify repeated references to the same object.
	WithStatement:
		with ( Expression ) BlockStatement
	
where Expression evaluates to an Object reference. Within the with body the referenced Object is searched first for identifier symbols. The with statement
	with (expression)
	{
	    ...
	    ident;
	}
	
is semantically equivalent to:
	{
	    Object tmp;
	    tmp = expression;
	    ...
	    tmp.ident;
	}
	
Note that expression only gets evaluated once. The with statement does not change what this or super refer to.

Synchronize Statement

The synchronize statement wraps a statement with critical section to synchronize access among multiple threads.
	SynchronizeStatement:
		synchronize Statement
		synchronize ( Expression ) Statement
	
synchronize allows only one thread at a time to execute Statement.

synchronize (Expression), where Expression evaluates to an Object reference, allows only one thread at a time to use that Object to execute the Statement.

The synchronization gets released even if Statement terminates with an exception, goto, or return.

Example:

	synchronize { ... }
	
This implements a standard critical section.

Try Statement

Exception handling is done with the try-catch-finally statement.
	TryStatement:
		try BlockStatement Catches
		try BlockStatement Catches finally BlockStatement
		try BlockStatement finally BlockStatement

	Catches:
		LastCatch
		Catch
		Catch Catches

	LastCatch:
		catch BlockStatement

	Catch:
		catch ( Parameter ) BlockStatement
	
Parameter declares a variable v of type T, where T is Object or derived from Object. v is initialized by the throw expression if T is of the same type or a base class of the throw expression. The catch clause will be executed if the exception object is of type T or derived from T.

If just type T is given and no variable v, then the catch clause is still executed.

It is an error if any Catch Parameter type T1 hides a subsequent Catch with type T2, i.e. it is an error if T1 is the same type as or a base class of T2.

LastCatch catches all exceptions.


Throw Statement

Throw an exception.
	ThrowStatement:
		throw Expression ;
	
Expression is evaluated and must be an Object reference. The Object reference is thrown as an exception.

Asm Statement

Inline assembler is supported with the asm statement:
	AsmStatement:
		{ }
		{ AsmInstructionList }

	AsmInstructionList:
		AsmInstruction ;
		AsmInstruction ; AsmInstructionList
	
An asm statement enables the direct use of assembly language instructions. This makes it easy to obtain direct access to special CPU features without resorting to an external assembler. The D compiler will take care of the function calling conventions, stack setup, etc.

The format of the instructions is, of course, highly dependent on the native instruction set of the target CPU, and so is implementation defined. But, the format will follow the following conventions:

These rules exist to ensure that D source code can be tokenized independently of syntactic or semantic analysis.

For example, for the Intel Pentium:

	int x = 3;
	asm
	{
	    mov	EAX,x		// load x and put it in register EAX
	}
	
D has no volatile storage type. Volatile is typically used to access hardware registers, so using inline assembler is appropriate for those cases, as in:
	int gethardware()
	{
	    asm
	    {
		    mov	EAX, dword ptr 0x1234;
	    }
	}
	

Copyright (c) 1999-2001 by Digital Mars, All Rights Reserved