Preliminaries
Initial Definitions
Statement
Declaration
Statement
Object
Constructor
Common Member
Method Prototype
Function Prototype
Definition
Statement
Namespace
Type Constructors
Class
Struct
Task
Frame
Module
Native Class Module
Native Task Module
Foreign Module
Union
Bitfields
Enumeration
Collection
Function Type
Typedef
Function Definition
Template Pattern
Template Type
Template Typedef
Graphical Interface
Plain Statement
Expression
Identifier
Constructor
Cast
New
Size
Operator
Call
Conditional
Signal
Hear
Action
Jump
Return
Break
Continue
Resume
Repeat
Delete
Destroy
Raise
Signal
Tell
Using
Endusing
Database
Select
Fetch
Free
Insert
Update
Remove
Control
Statement
If
Switch
Select
For
Do
While
Exception Layer
Operator Associativity and
Precedence
A program consists of one or more source files. The notion of
space is with regard to Linker's view of a program.
The global space (of a program) is the disjoint union of an
unnamed space and possibly one or more named spaces. When the set
of named spaces is empty, the unnamed space coincides with the
global space. The latter case is the reason for referring to the
unnamed space as the global space. We will continue to use the
term unnamed space for clarity.
Each source file intersects the unnamed/named spaces of the
program they constitute. This intersection is called the file
space. Compiler's view of space is limited to file space of the
file it is compiling. Clearly, Linker's global space is the union
of all the file spaces of source files in a program. For instance,
the union of unnamed spaces of all files spaces covers the
Linker's unnamed space.
The unnamed space is always present. In particular, the
definitions of named spaces, as well as the entry points of a
program, must appear in the unnamed space.
An engineer writes a program very much the same way a story or a
novel is written. Therefore, an important measure of the kinds of
stories that an engineer can write is the variety of statements in
the language (as well as parts of a statement, such as
expressions). The first rule (repeated here) enumerates the kinds
of statement.
statement --->>> +--> declaration-statement ---+---> | | +--> definition-statement ----> | | +--> plain-statement ---------> | | +--> control-statement ------->
A space (named/unnamed) can only contain statements of kind definition-statement
and declaration-statement. In fact, a definition-statement can only appear in a
space (named/unnamed).
We will use the term space to mean either the unnamed space or a specific named space.
Consider the definition of a class type as a representative for defining any type.
The part of definition between 'class' and 'end' is usually understood as the
definition of the class type. So, we know this part must appear in a space. What
we call the implementation of a class is actually the definitions of its methods.
So, the implementation of a class must also appear in the same space. This is not
an issue when the program consists of a single file.
However, when a program consists of several source files, only one of the files
can contain the implementation of the class. Compare this to an extern object. An
extern object can only be initialized in one of the files. The initialization of
an extern object is equivalent to providing the implementation of a class. Thus,
all class definitions in files that do not contain the class implementation are
implicitly extern. Implicit extern could have been applied to objects as well.
However, historically it was allowed to use the same identifier for objects in the
unnamed (global) space of different source files, without any connection among
those objects. As mentioned earlier, we are retaining this usage, for the entire
global space (named/unnamed).
In any space (named/unnamed) the Linker only considers the identifier for a type.
That means, all class definitions in all files of a program must be identical.
Verification of this matter takes a great deal of Linker's time, and is not done.
However, it is an extremely unlikely error to use another definition for the same
class in a different file. In particular, if it is really a different class, the
implementation must also be provided, in which case Linker will trap that as a
redefinition of the same type.
The order of initializing and extern object and using it is insignificant. Consider the following statements.
extern int i; int j = i;
Eventually the Linker will visit something like "int i = 7" in some file, otherwise you will get an error. The integer j will be correctly initialized to 7, regardless of, in which file the Linker finds the definition "int i = 7". This is true for user-defined types, like class.
The symbol <<<>>> defines a terminal. That is, the string on the left of the symbol is a terminal being defined by the right of the symbol. For definition of none-terminals we use the symbol
--->>>
Terminals of grammar are shown in all upper-case letters. However, Z++ keywords are in lower case. Terminals that are keywords are shown as BOLD. For instance, the terminal PUBLIC is the Z++ keyword public.
Z++ Compiler is case-sensitive. By a LETTER (of alphabet) we mean either an upper-case letter from 'A' to 'Z', or a lower-case letter from 'a' to 'z'.
A DIGIT is one of decimal digits from '0' to '9'.
The under-score symbol '_' is obtained by holding shift key down and pressing the minus sign. To avoid confusion, we show '_' with terminal UNDERSCORE.
Names of types, objects, namespaces, etc. are referred to as IDENTIFIER.
IDENTIFIER <<<>>> LETTER --+-----------------------------+---> | | +--+--+--> LETTER ------+--+--> ^ | | | | +--> DIGIT -------> | | | | | | +--> UNDERSCORE --> | | | <-----------------------+
Remark. A preprocessor identifier can also begin with any number of UNDERSCORE.
A literal value of any type, in general, is called a LITERAL.
DISCRETE-NUMERIC is a LITERAL of numeric types without a decimal point.
NUMERIC-LITERAL is either a DISCRETE-NUMERIC, or contains a decimal point. A NUMERIC-LITERAL may begin with + or - signs.
Literal values presented in the definition of an enumeration type are called
ENUM-LITERAL, and must begin with UNDERSCORE.
ENUM-LITERAL <<<>>> UNDERSCORE ---> IDENTIFIER
A CHAR-LITERAL is an ASCII charactor (on a typewriter) between single-quotes.
Thus, 'A' is a CHAR-LITERAL. With ECSAPE-CHARACTER, a CHAR-LITERAL will have
two charactors. For instance, the new-line charator is: '\n'. Special charactors
are also indicated with ECSAPE-CHARACTOR: '\'' is just the quote charactor,
and '\"' is the double-quote charactor.
A STRING-LITERAL is a sequence of ASCII charactors, between
double-quotes. For instance, "A%&k=}" is a STRING-LITERAL.
In a STRING-LITERAL, the ECSAPE-CHARACTOR has its usual meaning, same as it does
for CHAR-LITERAL. However, the single-quote in a STRING-LITERAL does not need
the ECSAPE-CHARACTOR.
QUOTE <<<>>> the symbol ' as in '\''
DOUBLE-QUOTE <<<>>> '"'
COLON <<<>>> ':'
SEMI-COLON <<<>>> ';'
COMMA <<<>>> ','
DOT <<<>>> '.'
LESS <<<>>> '<'
MORE <<<>>> '>'
RESOLVER <<<>>> "::"
ARROW <<<>>> "->"
DOUBLE-ARROW <<<>>> "->>"
Remark. DOUBLE-ARROW is for reaching a base of a pointer to an object, similar to the ARROW for reaching a member.
BACK-ARROW <<<>>> "<-"
Remark. BACK-ARROW is for signaling.
BELONG-OPERATOR <<<>>> +---> MEMBER-OPERATOR ---+--> | | +---> BASE-OPERATOR -----> MEMBER-OPERATOR <<<>>> +--> DOT ------------+---> | | +--> ARROW ----------> BASE-OPERATOR <<<>>> +--> RESOLVER -------> | | +--> DOUBLE-ARROW --->
There are two literal values for the boolean type: TRUE, FALSE. The actual literal
values in a Z++ program are 'True' and 'False'.
statement --->>> +--> declaration-statement ---+---> | | +--> definition-statement ----> | | +--> plain-statement ---------> | | +--> control-statement -------> statement-block --->>> --+---+--> declaration-statement ---+---+---> ^ | | | | +--> plain-statement ---------> | | | | | | +--> control-statement -------> | | | <-------------------------------------+
Remark. A statement-block is a subspace which allows declarations. For instance
the body of a function is a statement-block. An object declared in a
statement-block is said to have the scope of the subspace in which it is
declared. Specifically, within the subspace, its identifier overlays all
identical identifiers is spaces that contain that subspace.
statement-blocks can be nested to any depth. For instance legs of a
control-statement are themselves statement-blocks.
plain-statement --->>> +---> expression-statement ---+---> | | +---> action-statement -------> | | +---> database-statement ----->
declaration-statement --->>> +---> object-declaration --+--> SEMI-COLON | | +---> method-prototype ----> | | +---> function-prototype -->
object-declaration --->>> +-------> regular-declaration --------+---> | | +-----> constructor-declaration ------> | |
+---> common-member-initialization --->
regular-declaration --->>> declaration-head --+--> declaration-body --+--> | | <------- COMMA <--------+ declaration-head --->>> +------------------------------+--> declaration-type ----> | | +---> EXTERN ---+--------------> | | | +---------------+---> CONST ---> declaration-type --->>> --+--> scope-space ----> defined-type --------+--> | | +------------> instantiated-type -----------> | | +------------> fundamental-type ------------>
Remark. defined-type is the type-name appearing in one of previously visited forms of type-definition statements.
fundamental-type --->>> --+---> CHAR -----+---> | | +---> UCHAR ----> | | +---> SHORT ----> | | +---> USHORT ---> | | +---> INT ------> | | +---> UINT -----> | | +---> LONG -----> | | +---> ULONG ----> | | +---> FLOAT ----> | | +---> DOUBLE ---> | | +---> STRING ---> | | +--> BOOLEAN ---> | | +---> MUTEX ---->
Remark. void is also a built-in type, shown as VOID in rules. Objects of type void cannot be created.
<-------------------------------+
| |
+--+--> space-name ---> RESOLVER --+-->
| |
scope-space --->>> +-+-------------------------------------+--+-->
| |
+----------------> RESOLVER -------------->
Remark. RESOLVER, by itself, indicates the unnamed space portion of global space.
space-name is an IDENTIFIER, name of a defined namespace.
declaration-body --->>> +----------+---+-----------+--> object-name ---> declaration-tail ---> | | | | +-+-> * -+-> +---> & ----> | | <------+
Remark. object-name is an IDENTIFIER.
declaration-tail --->>> -+-----------------------+--+-------------------+--> | | | | +--> array-modifier ----> +--> initializer ---> array-modifier --->>> +--> [ ---> array-index ---> ] --+--+----------+--+----------+---> | | | | | | <--------------------------------+ +-+-> * -+-> +---> & ---> | | <------+ array-index --->>> +------------------------+----> | | +---> DISCRETE-NUMERIC---> | | +---> DECLARED-OBJECT --->
Remark. The empty case is for degenerate array kind. DECLARED-OBJECT is an IDENTIFIER for a previously declared object.
initializer --->>> expression-statement
Remark. Array of references is not permissible. However, a reference to an array is allowed, in which case the reference symbol & must come after array index. A reference always requires an initializer.
constructor-declaration --->>> declaration-type ---> object-name ---> ( --+--> argument --+--> ) ---> | | <--- COMMA -----+ argument --->>> expression-statement
Remark. A constructor declaration requires arguments. The default constructor is invoked via regular declaration, without the use of ().
// Below, k is a reference to j, p points to k, q is a reference to p. int i = 5, j = i + 2, &k = j, *p = &k, *&q = p; // The following three declarations are equivalent. double d = 4.7; double d = double(4.7); double d(4.7); // constructor declaration short s; // default initializer is 0 // Below, all cells of static array a are initialized to 97. // r is a reference to array a, and q points to r. int a[2][3] = 97, r[2][3]& = a, q[2][3]* = &r; // Operator [] has higher priority than *. So, to output a cell use parentheses. // Otherwise, you will be derefencing the cell at [0][1], instead of the array. output << (*q)[0][1] << '\n'; // Reference and pointer to a dynamic array must be degenerate, without dimensions. int m = 3, n = 5; int a[m][n] = 97, r[][]& = a, p[][]* = &r, q[][]*& = &r; // q is a reference to a pointer to an array. So, at the end, it is a pointer // to an array, making the use of parentheses necessary as previous example. output << (*q)[0][1] << '\n'; // For a none-trivial constructor declaration, consider the following definition. struct example int* p; example(int*); end; // Below is a constructor declaration, using new. example e(new int(7));
definition-statement --->>> +--> namespace-definition ---+-->
| |
+--> entity-definition ------>
entity-definition --->>> +--> type-definition ----------------->
| |
+--> typedef-definition -------------->
| |
+--> function-definition ------------->
| |
+--> pattern-definition -------------->
| |
+--> template-type-definition -------->
| |
+--> template-function-definition ---->
| |
+--> template-typedef-definition ----->
| |
+--> graphical-interface-definition -->
Remark. A definition-statement can only appear in global space. More specifically, namespace-definition can only appear in the unnamed global space, while entity-definition can also apear in a named space. Basically, namespace definitions cannot be nested.
A namespace definition can also include the definition of functions and methods.It is also possible to split the definition of a namespace, and put the definition of functions and methods in the implementation part of a namespace. This is useful when a project consists of several source files which use the same namespace. In that case the implementation part of namespace needs to be included in only one of the source files.
namespace-definition --->>> +--> namespace-main-definition ---------------+---->
| |
+--> namespace-implementation-definition ----->
namespace-main-definition --->>> --+----------------+--> NAMESPACE --> namespace-name --+---------------------------+--> namespace-body --> ENDSPACE --> SEMI-COLON
| | | |
+--> PROTECTED --> +--> namespace-derivation -->
Remark. namespace-name is an IDENTIFIER for name of namespace being defined. A protected namespace can only be derived from. It cannot be opened directly.
namespace-derivation --->>> COLON --+--+------------------------+--> previously-defined-namespace --+-->
| | | |
| +--> derivation-access --> |
| |
<-------------------- COMMA <---------------------------------+
derivation-access --->>> +--> PRIVATE -----+---> | | +--> PUBLIC ------>
Remark. previously-defined-namespace is an IDENTIFIER, for namespace-name of an already defined namespace. Default derivation-access is public.
namespace-body --->>> ---+--+---> declaration-statement --+--+--->
^ | | |
| +---> entity-definition ------> |
| | | |
| +---> access ---> COLON ------> |
| |
<-----------------------------------+
access --->>> +--> PRIVATE -----+---> | | +--> PROTECTED ---> | | +--> PUBLIC ------>
Remark. Default access for namespace is public.
namespace-implementation-definition --->>> IMPLEMENTATION --+-------------------------+--> namespace-name ---> implementation-body --> ENDSPACE --+-------------------------+--> SEMI-COLON
| | | |
+--> LESS TEMPLATE MORE --> +--> LESS TEMPLATE MORE -->
Remark. namespace-name is the IDENTIFIER that appeared as namespace-name in namespace-main-definition for this namespace. When the implementation of a namespace contains template definitions, it takes the form: implementation<template> ... endspace<template>;
implementation-body --->>> --+--+--> function-definition ------------+--+--->
| | | |
| +--> template-function-definition ---> |
| |
<------------------------------------------+
A type constructor is a mechanism for defining new types. For instance, class,
task etc. are type constructor mechanisms.
Function types are defined via prototype mechanism.
type-definition --->>> +--> class-definition ---------+---> END --+---> SEMI-COLON | | ^ +--> struct-definition --------> | | | | +--> task-definition ----------> | | | | +--> frame-definition ---------> | | | | +--> module-definition --------> | | | | +--> union-definition ---------> | | | | +--> bitfields-definition -----> | | | +--> enumeration-definition ---------------> | | +--> collection-definition ---------------->
| |
+--> function-type-definition ------------->
class-definition --->>> CLASS --> type-name --+-----------------+-> class-body --> | | +--> derivation -->
Remark. type-name is an IDENTIFIER for name of the type being defined.
derivation --->>> COLON --+--+------------------------+--> declaration-type --+--> | | | | | +--> derivation-access --> | | | <--------------------- COMMA <--------------------+
Remark. Default derivation-access is public.
class-body --->>> --+---+----------------------------+--+---> ^ | | | | +---> access ---> COLON -----> | | | | | | +---> class-method ----------> | | | | | | +---> type-member -----------> | | | | | | +---> invariant-statement ---> | | | <-----------------------------------+
Remark. Default access for class is private.
type-member --->>> +--> regular-member --+--> | | +--> friend-member ---> | | +--> common-member ---> regular-member --->>> member-head ---> declaration-type --+----------+--> member-name --> member-tail --> SEMI-COLON | | +-+-> * -+-> | | <------+ member-head --->>> --+------------+--> | | +--> CONST -->
Remark. member-name is an IDENTIFIER for the name of a data member of the type being defined.
member-tail --->>> --+-------------------+--+------------------------------+---> | | | | +--> member-array --> +-----> LESS VISIBLE MORE ----->
Remark. A private/protected visible member can be accessed as read-only.
member-array --->>> +--> [ ---> member-array-index ---> ] --+--+----------+---> | | | | <---------------------------------------+ +-+-> * -+-> | | <------+ member-array-index --->>> +------------------------+----> | | +---> DISCRETE-NUMERIC -->
Remark. Dynamic array members use empty index, [], for each dimension.
A dynamic array member must be initialized via constructors.
In presence of dynamic array members, Compiler requires constructors to
be defined by user.
friend-member --->>> FRIEND --> regular-member
Remark. Methods of a friend member have the same access permission as methods of the type being defined.
common-member --->>> COMMON --> regular-member --+-----------------------------------+---> | | +--> COLON --> authorized-methods --> authorized-methods --->>> --+--> pure-prototype --+--> | | <------- COMMA -------+
Remark. A common member is shared by all instances of the type.
A common member can only be modified by an authorized method. Thus, if no
authorized method is listed for a common member, it is implicitly constant.
A common member cannot be public.
A common member cannot be initialized/destroyed by an instance of the type.
common-member-initialization --->>> declaration-type ---> common-name = ---> initializer --->
common-name --->>> scope-space ---> type-name ---> RESOLVER ---> member-name --->
Remark. member-name is IDENTIFIER for name of common member, and type-name is IDENTIFIER for the name of type that owns the common member.
A common member is destroyed when the global space ends. However, when a common member is dynamically initialized, it must be deleted/destroyed explicitly.
The rule common-member-initialization is not needed. However, it shows common-name more clearly.
Example
#include<iostream.h>
using namespace ioSpace;
// Put definitions in a namespace
namespace CommonSpace
// CommonBase has two simple common members
class CommonBase
common int dc<visible> : changeCommon(int);
double d<visible>;
common int* cptr<visible> : changePtr(int*);
public:
CommonBase(double);
void changeCommon(int);
void changePtr(int*);
end;
// CommonOwner has CommonBase as a common member
class CommonOwner
common CommonBase cbm<visible>;
double d;
public:
CommonOwner(double);
void changeCommon(int);
end;
// Definitions of methods
CommonOwner::CommonOwner(double b)
d = b;
end;
CommonBase::CommonBase(double b)
d = b;
end;
void CommonBase::changeCommon(int b)
dc = b;
end;
void CommonBase::changePtr(int* p)
cptr = p;
end;
void CommonOwner::changeCommon(int b)
cbm.changeCommon(b);
end;
// Initializing common members inside namespace
int CommonBase::dc = 47;
int* CommonBase::cptr = new int(7);
endspace;
// Initializing common member outside of namespace
CommonSpace::CommonBase CommonSpace::CommonOwner::cbm = CommonSpace::CommonBase(44.77);
//---------------------------------------------------------------------------//
entry void main(void)
output << "Hello World!\n";
output << CommonSpace::CommonOwner(13.5).cbm.dc << '\n';
CommonSpace::CommonOwner(1.5).changeCommon(97);
int cmn = CommonSpace::CommonOwner(13.5).cbm.dc;
output << cmn << '\n';
CommonSpace::CommonOwner CC(13.5);
output << *CC.cbm.cptr << '\n';
delete CommonSpace::CommonBase::cptr;
output << "Good-bye World!\n";
end;
output of program is:
Hello World!
47
97
7
Good-bye World!
invariant-statement --->>> INVARIANT ( --> boolean-expression --> ) --> violation-action --> SEMI-COLON
Remark. boolean-expression is an expression-statement that results in an object of type boolean.
boolean-expression for invariant-statement must involve members of class.
struct-definition --->>> STRUCT --> type-name --+----------------+-> class-body --> | | +-> derivation -->
Remark. The only difference between struct and class is that the default access
for struct is public.
task-definition --->>> TASK --> type-name --+-----------------+--> task-body --> | | +--> derivation --> task-body --->>> -+---+----------------------------+--+---> ^ | | | | +---> access ---> COLON -----> | | | | | | +---> task-method -----------> | | | | | | +---> type-member -----------> | | | | | | +---> invariant-statement ---> | | | | | | +---> accepts-list ----------> | | | | | | +---> hears-list ------------> | | | <-----------------------------------+
Remark. Default access for task is private.
frame-definition --->>> FRAME --> type-name --> := canvas-name --+-----------------+-> frame-body --> | | +--> derivation --> frame-body --->>> --+---+----------------------------+--+---> ^ | | | | +---> access ---> COLON -----> | | | | | | +---> frame-method ----------> | | | | | | +---> type-member -----------> | | | | | | +---> invariant-statement ---> | | | <-----------------------------------+
Remark. Default access for frame is private. A frame must have an instinct method. canvas-name is IDENTIFIER for the name of canvas associated with the frame being defined.
module-definition --->>> +---> native-class-module --+---> | | +---> native-task-module ---> | | +---> foreign-module -------> native-class-module --->>> +--> CLASS ---+--> type-name --> native-module-head -+-----------------+-> native-class-module-body --> | | | | +--> STRUCT --> +--> derivation -->
native-task-module --->>> TASK --> type-name --> native-module-head -+-----------------+-> native-task-module-body --> | | +--> derivation --> native-module-head --->>> = ---> module-path --+---------------------+---> | | +--> execution-node --> module-path --->>> DOUBLE-QUOTE ---+------------------------+---> FULL-PATH-FILE-NAME ---> DOUBLE-QUOTE --> | | +--> ZPP://IP-ADDRESS/ -->
Remark. In "ZPP://IP-ADDRESS/" for module-path, the ZPP is not case-sensitive. IP-ADDRESS is location of node we wish to communicate with. FULL-PATH-FILE-NAME is relative to the node on which the module executable resides.
execution-node --->>> LESS ---+---> LOCAL ---+---> MORE ---> | | +---> REMOTE -->
Remark. Default for execution-node is local. execution-node is the node on which module executable will begin execution as a process of that node. Thus, module-path is the location of executable as a file, while execution-node is where the file will be loaded as a process.
The execution-node only makes sense when module-path conatains an IP-ADDRESS. In that case, local execution means downloading the executable to the local node and starting it as a local process. On the other hand, remote execution means that the remote node which has the executable, will load the executable as a (remote) process.
native-class-module-body --->>> --+---+----------------------------------+--+--> ^ | | | | +---> access ---> COLON -----------> | | | | | | +---> native-class-module-mthod ---> | | | | | | +---> type-member -----------------> | | | | | | +---> invariant-statement ---------> | | | <-----------------------------------------+ native-task-module-body --->>> --+---+----------------------------------+--+--> ^ | | | | +---> access ---> COLON -----------> | | | | | | +---> native-task-module-method ---> | | | | | | +---> type-member -----------------> | | | | | | +---> invariant-statement ---------> | | | <-----------------------------------------+
foreign-module --->>> +---> dynamic-module ---+---> | | +---> static-module ----> dynamic-module --->>> +--> CLASS ---+--> type-name --> dynamic-module-head -+-----------------+-> dynamic-module-body --> | | | | +--> STRUCT --> +--> derivation --> dynamic-module-head --->>> foreign-specification --> load-specification --> = --> FOREIGN-LIBRARY -->
Remark. FOREIGN-LIBRARY is full-path filename to the DLL or Loadable-module to link with. It is a string literal between quotes, as in "library-name.dll".
foreign-specification --->>> +--> "C++" ----+---> | | +--> "UNIX" --->
Remark. foreign-specification indicates the language and kind of dynamic library
to link with. Currently the foreign language is C/C++. "C++" means link
with DLL, and "UNIX" means link with loadable modules.
load-specification --->>> ---+----------------------------------+---> | | +--> GENERATED --+-----------------> | | +--> PERSISTENT -->
Remark. persistent means, load the library once, as opposed to each call to an
external method. Then library is unloaded when representing object goes
out of scope.
generated in this context tells the compiler not to regenerate the Z++
equivalent module for the foreign dynamic library being linked with. The order of generated and persistent is not significant.
dynamic-module-body --->>> generated-statement ---> foreign-module-body ---> generated-statement --->>> GENERATED ---> LIBRARY-PATH-NAME ---> SEMI-COLON
Remark. LIBRARY-PATH-NAME is full-path and name of library (without file extension)
in target language to be generated by Z++ Compiler. For instance, if linkage
is with a C++ DLL, do not include the ".dll" in filename. The Z++ Compiler
will add file extension as needed.
LIBRARY-PATH-NAME is a string literal between quotes, as in "path-name".
foreign-module-body --->>> --+---+---------------------------------+--+--> ^ | | | | +---> access ---> COLON ----------> | | | | | | +---> foreign-module-method ------> | | | | | | +---> type-member ----------------> | | | | | | +---> invariant-statement --------> | | | <----------------------------------------+
static-module --->>> +--> CLASS ---+--> type-name --> static-module-head --+-----------------+--> static-module-body --> | | | | +--> STRUCT --> +--> derivation --> static-module-head --->>> load-specification ---> STATIC --->
Remark. The order of generated, persistent and static is not significant.
static-module-body --->>> --+------------------------+---> foreign-module-body ---> | | +--> library-statement --> library-statement --->>> LIBRARY --+--> FOREIGN-HEADER ---+--> SEMI-COLON | | <------- COMMA ------+
Remark. An example of FOREIGN-HEADER is "math.h".
union-definition --->>> UNION ---> type-name --> union-members ---> union-tail ---> union-members --->>> --+--> member-type ---> member-name ---> SEMI-COLON --+--> | | <---------------------------------------------------+
union-tail --->>> --+--+------------------------+--+--> | | | | | +--> access --> COLON ---> | | | | | | +--> union-method -------> | | | <------------------------------+ union-method --->>> +---> regular-prototype -------+--> | | +---> constructor-prototype ---> | | +---> destructor-prototype ----> | | +---> conversion-prototype ---->
Remark. member-type is either a numeric fundamental type, or pointer to any type.
Default access for union is public.
bitfields-definition --->>> BITFIELDS --> type-name --> bitfields-members --> bitfields-tail ---> bitfields-members --->>> --+--> member-name ---> member-bits ---> SEMI-COLON --+--> | | <---------------------------------------------------+
bitfields-tail --->>> union-tail
Remark. member-bits is a none-zero literal of fundamental type uint.
Default access for bitfields is public.
enumeration-definition --->>> ENUM ---> type-name --+--------------------------+--> { --> enum-value-list --> } --> | |
+--> COLON --> enum-base -->
enum-base --->>> scope-space ---> enum-base-type --->
enum-value-list --->>> --+--> enum-value --+--> | | <----- COMMA -----+ enum-value --->>> ENUM-LITERAL --+-----------------------------+---> | | +--> = --> DISCRETE-NUMERIC -->
Remarks. enum-base-type is a previously defined enumeration type, which will be included in the new enumeration type being defined. However, an extended type cannot contain values of its base type. Default DISCRETE-NUMERIC for the first ENUM-LITERAL of an enumeration type without a base is 0, with default increment of 1 for succeding values.
Numeric values assigned by user must be in increasing order. The default integer value for the first ENUM-LITERAL of an extended type is 1 more than the last (largest) integer values of its base.
ENUM-LITERAL of an enumeration type can be reused in defining other unrelated enumeration types.
collection-definition --->>> COLLECTION --> type-name --> collection-head --> { --> collection-tail --> } --> collection-head --->>> LESS --> collection-enum --> MORE -+--------------------------------+--> | | +--> COLON --> collection-base --> collection-enum --->>> scope-space ---> enum-type --+---------------------------+--> | | +--> collection-enum-tail --> collection-enum-tail --->>> COLON +--> tag-value-access --+-------------------------+--+--> | | | | | +--> COMMA ---> VISIBLE --> | | | +--> VISIBLE --+----------------------------------+--> | | +--> COMMA ---> tag-value-access --> tag-value-access --->>> +--> PROTECTED ---+---> | | +--> PUBLIC ------> collection-base --->>> scope-space ---> collection-type --->
Remarks. enum-type is a previously defined enumeration type. collection-type is a previously defined collection type. The collection-enum of a derived collection must be an extension of the collection-enum of its collection-base.
The term tag refers to the instance or the current value of collection-enum, maintained internally by the instance of collection. On the other hand, the term value refers to an index-value, defined below.
The default access for tag and value is public, therefore they are visible unless made protected.
collection-tail --->>> collection-index-value ---> collection-body --->
collection-index-value --->>> --+--> ENUM-LITERAL ---> LESS ---> index-value ---> MORE --+-->
| |
<---------------------- COMMA <--------------------------+
Remark. ENUM-LITERAL are values of collection-enum. All literals of collection-enum must have an index-value.
An index-value is an IDENTIFIER for the name of a class/struct type. So, collection-index-value defines a one-to-many correspondence, associating a class type to each value of collection enumeration.
An index-value is the class type associated to an ENUM-LITERAL of collection-enum.
collection-body --->>> --+--+------------------------+--+--+-----------------------+--+--+------------------------+--+--> | | | | | | | | | | | +--> access --> COLON ---> | +--> SHARED --> COLON --> | +--> access --> COLON ---> | | | | | | | | | | +--> union-method -------> | | +--> union-method -------> | | | | | <------------------------------+ <------------------------------+
Remark. Once shared has been visited, the shared section begins and extends to
end of definition.
Default access for collection is public.
Example: Collection Type Constructor
#include<iostream.h>
using namespace ioSpace;
///////////////////////////////////////////////////////////////////////////////
// This example shows some the uses of the type constructor collection. The
// mechanism allows defining enumerations which can take on class types for
// their values, rather than just integers.
//---------------------------------------------------------------------------//
// Collections can be derived from one another. In this example we will define
// three collection types as follows.
//
// basicFiguresType starting base
// moreFiguresType derived from basicFiguresType
// mostFiguresType derived from moreFiguresType
//
///////////////////////////////////////////////////////////////////////////////
//---------------------------------------------------------------------------//
// A collection is associated with an enumeration. First we define an enum, and
// then we define three classes, Square, Rectangle and Triangle to set as
// values for the enumeration literals.
//---------------------------------------------------------------------------//
enum basicFigures {_square, _rectangle, _triangle};
//---------------------------------------------------------------------------//
// We derive all figures from class Shape, for simplicity.
//---------------------------------------------------------------------------//
class Shape
protected:
double firstDim;
double secondDim;
public:
Shape(double, double);
double area(void);
end;
//---------------------------------------------------------------------------//
Shape::Shape(double f, double s)
firstDim = f;
secondDim = s;
end;
double Shape::area(void)
return firstDim * secondDim;
end;
///////////////////////////////////////////////////////////////////////////////
// The value Square for collection.
//---------------------------------------------------------------------------//
class Square : Shape
public:
Square(double);
double area(void);
double ShowArea(void);
end;
//---------------------------------------------------------------------------//
Square::Square(double d) : Shape(d, d)
end;
double Square::area(void)
return Shape::area();
end;
double Square::ShowArea(void)
double myArea = Shape::area();
output << "I am Square and my area is: " << myArea << '\n';
return myArea;
end;
///////////////////////////////////////////////////////////////////////////////
// The value Rectangle for collection.
//---------------------------------------------------------------------------//
class Rectangle : Shape
public:
Rectangle(double, double);
double area(void);
double ShowArea(void);
end;
//---------------------------------------------------------------------------//
Rectangle::Rectangle(double f, double s) : Shape(f, s)
end;
double Rectangle::area(void)
return Shape::area();
end;
double Rectangle::ShowArea(void)
double myArea = Shape::area();
output << "I am Rectangle and my area is: " << myArea << '\n';
return myArea;
end;
///////////////////////////////////////////////////////////////////////////////
// The value Triangle for collection.
//---------------------------------------------------------------------------//
class Triangle : Shape
public:
Triangle(double, double);
double area(void);
double ShowArea(void);
end;
//---------------------------------------------------------------------------//
Triangle::Triangle(double f, double s) : Shape(f, s)
end;
double Triangle::area(void)
return Shape::area()/2;
end;
double Triangle::ShowArea(void)
double myArea = Shape::area()/2;
output << "I am Triangle and my area is: " << myArea << '\n';
return myArea;
end;
///////////////////////////////////////////////////////////////////////////////
// Now that we have all the ingredients, we can define the collection type.
//---------------------------------------------------------------------------//
// The type collection being defined is "basicFiguresType", and its enumeration
// is basicFigures. To each enum literal, we associate a class.
//---------------------------------------------------------------------------//
// Collection can also have its own methods. A shared method is invoked on all
// values of collection.
///////////////////////////////////////////////////////////////////////////////
collection basicFiguresType<basicFigures> {
_square<Square>,
_rectangle<Rectangle>,
_triangle<Triangle>
basicFiguresType(double, double, double, double);
basicFiguresType& operator=(const basicFiguresType&);
boolean operator==(const basicFiguresType&) const;
shared:
double ShowArea(void);
};
//---------------------------------------------------------------------------//
basicFiguresType& basicFiguresType::operator=(const basicFiguresType& e)
self[_square] = e[_square];
self[_rectangle] = e[_rectangle];
self[_triangle] = e[_triangle];
return self;
end;
//---------------------------------------------------------------------------//
boolean basicFiguresType::operator==(const basicFiguresType& e)
if (self[_square] != e[_square]) return False;
elsif (self[_rectangle] != e[_rectangle]) return False;
elsif (self[_triangle] != e[_triangle]) return False;
else return True;
endif;
end;
//---------------------------------------------------------------------------//
basicFiguresType::basicFiguresType(double s, double l, double w, double h)
: Square(s), Rectangle(l, w), Triangle(l, h)
end;
///////////////////////////////////////////////////////////////////////////////
// Now we want to derive a new collection type from basicFiguresType. To do so
// we first derive a new enum from the enum of basicFiguresType.
///////////////////////////////////////////////////////////////////////////////
enum moreFigures : basicFigures {_parallelogram, _diamond};
///////////////////////////////////////////////////////////////////////////////
// Next, we define two classes for the values of the derived collection.
//---------------------------------------------------------------------------//
class Parallelogram : Shape
public:
Parallelogram(double, double);
double area(void);
double ShowArea(void);
end;
//---------------------------------------------------------------------------//
Parallelogram::Parallelogram(double f, double s) : Shape(f, s)
end;
double Parallelogram::area(void)
return Shape::area();
end;
double Parallelogram::ShowArea(void)
double myArea = Shape::area();
output << "I am Parallelogram and my area is: " << myArea << '\n';
return myArea;
end;
///////////////////////////////////////////////////////////////////////////////
// The value Diamond for collection.
//---------------------------------------------------------------------------//
class Diamond : Shape
public:
Diamond(double, double);
double area(void);
double ShowArea(void);
end;
//---------------------------------------------------------------------------//
Diamond::Diamond(double f, double s) : Shape(f, s)
end;
double Diamond::area(void)
return Shape::area();
end;
double Diamond::ShowArea(void)
double myArea = Shape::area();
output << "I am Diamond and my area is: " << myArea << '\n';
return myArea;
end;
///////////////////////////////////////////////////////////////////////////////
// Now we are ready to derive moreFiguresType from collection basicFiguresType.
//---------------------------------------------------------------------------//
// The enum associated with moreFiguresType is moreFigures, and we only need
// to associate values to the new enum literals of moreFigures.
//---------------------------------------------------------------------------//
// The methods of a collection are virtual, just as they are for classes. That
// includes shared methods.
///////////////////////////////////////////////////////////////////////////////
collection moreFiguresType<moreFigures> : basicFiguresType {
_parallelogram<Parallelogram>,
_diamond<Diamond>
moreFiguresType(double s, double l, double w, double h, double r);
};
//---------------------------------------------------------------------------//
moreFiguresType::moreFiguresType(double s, double l, double w, double h, double r)
: basicFiguresType(s, l, w, h), Parallelogram(w, r), Diamond(h, r)
end;
///////////////////////////////////////////////////////////////////////////////
// For our next level of derivation we will only add one more value.
///////////////////////////////////////////////////////////////////////////////
enum mostFigures : moreFigures {_circle};
///////////////////////////////////////////////////////////////////////////////
class Circle : Shape
public:
Circle(double);
double area(void);
double ShowArea(void);
end;
//---------------------------------------------------------------------------//
Circle::Circle(double r) : Shape(r, r)
end;
double Circle::area(void)
return Shape::area() * 3.14;
end;
double Circle::ShowArea(void)
double myArea = Shape::area() * 3.14;
output << "I am Circle and my area is: " << myArea << '\n';
return myArea;
end;
///////////////////////////////////////////////////////////////////////////////
// Now we derive the next level of collection, mostFiguresType.
//---------------------------------------------------------------------------//
// This collection has a new method of its own, PrintAtTag().
//---------------------------------------------------------------------------//
collection mostFiguresType<mostFigures> : moreFiguresType {
_circle<Circle>
mostFiguresType(double, double, double, double, double, double);
void PrintAtTag(void);
};
//---------------------------------------------------------------------------//
mostFiguresType::mostFiguresType(double s, double l, double w, double h, double r, double c)
: moreFiguresType(s, l, w, h, r), Circle(c)
end;
//---------------------------------------------------------------------------//
// This method illustrates how to use collection in a manner similar to a tagged
// union. However, values in a collection are distinct objects.
//---------------------------------------------------------------------------//
// The expression [self] is the current literal value of the enumeration associated
// to the collection, which we are refering to as the tag of the collection.
//---------------------------------------------------------------------------//
// The bracket function applied to a collection object evaluates to the literal
// value of its associated enumeration value. In this example we are applying
// the bracket function to "self", which refers to the collection owning the
// method we are in.
//---------------------------------------------------------------------------//
// Another use of [] for collection is similar to array index. For instance,
// "self[_squaer]" used below evaluates to the instance of class (value) of
// collection at index (tag) _square. In this example this value is the instance
// of class Square that was initialized by constructor of collection.
//---------------------------------------------------------------------------//
void mostFiguresType::PrintAtTag(void)
output << "Area at current tag.\n";
switch([self])
case _square:
output << "Area of square is : " << self[_square].area() << '\n';
case _rectangle:
output << "Area of rectangle is : " << self[_rectangle].area() << '\n';
case _triangle:
output << "Area of triangle is : " << self[_triangle].area() << '\n';
case _parallelogram:
output << "Area of parallelogram is : " << self[_parallelogram].area() << '\n';
case _diamond:
output << "Area of diamond is : " << self[_diamond].area() << '\n';
case _circle:
output << "Area of circle is : " << self[_circle].area() << '\n';
else output << "Somehow got here.\n";
endswitch;
end;
///////////////////////////////////////////////////////////////////////////////
// The entry point main().
///////////////////////////////////////////////////////////////////////////////
entry void main(void)
output << "Hello World!\n";
mostFiguresType Figures(12, 11, 10, 8, 4, 4);
Figures.ShowArea(); // The shared method is applied to all values
output << "Good-bye World!\n";
end;
The output of this example is as follows.
Hello World!
I am Circle and my area is: 50.24
I am Parallelogram and my area is: 40
I am Diamond and my area is: 32
I am Square and my area is: 144
I am Rectangle and my area is: 110
I am Triangle and my area is: 44
Good-bye World!
function-type-definition --->>> TYPE --+--> regular-function-type -------+--> SEMI-COLON | | +--> instantiated-function-type --> regular-function-type --->>> TYPE ---> function-prototype ---> SEMI-COLON
Remark. Note that function-prototype can have THREAD, THROWS, ACCEPTS and HEARS.
However, identifiers for names of parameters are not accepted for TYPE.
The name of function-prototype becomes the name of function pointer type.
type int MyFunType(int, long); // define a function pointer type // receiver() will accept a function pointer, and use it to make a call. void receiver(MyFunType f) f(2, 3); end; // We will use globalFun to initialize a function pointer. int globalFun(int i, long g) // some statements return i; end; MyFunType MyFun = globalFun; //make and initialize an instance of MyFunType receiver(MyFun); //pass the instance of MyFunType to a function
instantiated-function-type --->>> function-name --+------------------------------+--> instantiated-function-call --> SEMI-COLON | | +--> COMMA --> function-type -->
Remark. function-name and function-type are IDENTIFIERs, and function-type is optional.
Generally, we are interested in function-name, which allows us to make function calls without the instantiation syntax. That is similar to typedef. However, if we also provide function-type, the statement will create a function pointer type as in function-type-definition.
#include<iostream.h> using namespace ioSpace; // Define template functions with same name and signature, one in a namespace and // the other in global space. namespace myspace template<type U> U fun(U n) return n += 3; end; endspace; template<type U> U fun(U n) return n *= 3; end; // Suppose we want to instantiate function types in different namespaces. // spaceFun is the name of instantiated function pointer, and spaceFunType // is its type. // myspace::fun<int> is an example for instantiated-function-call. namespace firstSpace type spaceFun , spaceFunType myspace::fun<int>; endspace; // globalFun is the name of instantiated function pointer, and globalFunType // is its type. // fun<int> is an example for instantiated-function-call. namespace secondSpace type globalFun , globalFunType fun<int>; endspace; // Using function pointer types, we defined the following two functions. void firstFun(firstSpace::spaceFunType f) output << "In firstFun...\n"; output << f(7) << '\n'; // 10 end; void secondFun(secondSpace::globalFunType f) output << "In secondFun...\n"; output << f(7) << '\n'; // 21 end; // ------------------------------------------------------------------------- // entry void main(void) output << "Hello World!\n"; // First, we call function pointers directly, Generally, this is how they are used. output << firstSpace::spaceFun(7) << '\n'; // 10 output << secondSpace::globalFun(7) << '\n'; // 21 // Next, we pass the function pointers as argument to the functions we defined earlier. firstFun(firstSpace::spaceFun); secondFun(secondSpace::globalFun); output << "Good-bye World!\n"; end;
The output of the example is: Hello World! 10 21 In firstFun... 10 In secondFun... 21 Good-bye World!
typedef-definition --->>> TYPEDEF ---> typedef-name ---> typedef-type ---> SEMI-COLON
Remark. typedef-name is an IDENTIFIER, which is used in a declaration as a name
for typedef-type.
<----------------------------------------------------------+ | | typedef-type --->>> declaration-type --+--+----------+---+-------------------------------------+--+--> | | | | +-+-> * -+-> +-+--> [ ---> array-index ---> ] --+--> | | | | <------+ <--------------------------------+
function-definition --->>> +--> global-function-definition ---+--> | | +--> method-definition ------------> global-function-definition --->>> function-prototype-declaration ---> statement-block ---> END ---> SEMI-COLON
Remark. Unlike methods, global functions are not required to have a prototype
prior to their definition. In such cases, function-tail can appear at
the definition of global functions. However, when a prototype exists,
function-tail must only appear at prototype.
When prototype and definition are separated, parameter initializers can
only appear in prototype, not the definition of function.
method-definition --->>> type-name --> RESOLVER --> shortened-method-prototype --> statement-block --> END
Remark. The rule shortened-method-prototype is easier to describe rather than listing a long set of rules.
Begin with rule method-prototype, for any type of method, such as regular-prototype, constructor-prototype, etc.
Cut the rule right after the closing parenthesis for parameter-list. That is, method-definition does not take specifiers like CONST, CAST, throws-list, etc.
Parameter initializers cannot appear in method-definition. Initializers can only appear in method-prototype.
External-methods do not have explicit definition. Compiler generates their definitions implicitly.
Friend-prototypes are not recorded as methods. The definition for a friend-prototype is just global-function-definition.
A template pattern is used to specify types that are allowed to instantiate a
template type.
pattern-definition --->>> PATTERN --> pattern-parameter --> pattern-name --> pattern-body --> END --> SEMI-COLON
Remark. pattern-name is an IDENTIFIER for name of pattern being defined.
pattern-parameter --->>> TEMPLATE --> LESS --> pattern-parameter-id --> MORE -->
Remark. pattern-parameter-id is an IDENTIFIER.
All occurrence of pattern-parameter-id within the definition of a pattern
are to the one in pattern-parameter. A pattern has only a single parameter.
pattern-body --->>> --+--> pattern-type-body ----+--->
| |
+--> pattern-method-body -->
pattern-type-body --->>>> --+--> pattern-parameter-id --> COLON --+--> declaration-type --+---> SEMI-COLON --+--> | | | | | <--------- COMMA <------+ |
| |
<--------------------------------------------------------------------------------+
pattern-method-body --->>> --+--> pattern-method ---> SEMI-COLON --+--> | | <-------------------------------------+ pattern-method --->>> +--> pattern-type --> pattern-method-name --> pattern-method-parameter-list --+-> | | +---> OPERATOR ---> pattern-method-parameter-list ---> ( ---> VOID ---> ) ----> pattern-type --->>> --+------------+--+-----> declaration-type ---+--> parameter-specifier ---> | | | | +--> CONST --> +--> pattern-parameter-id --> pattern-method-name --->>> +--> IDENTIFIER -----------------------+--> | | +---> OPERATOR ---> operator-symbol ---> | | +------> ? -------------------------->
Remark. The ? means the name of method is irrelevant. Thus, ? is a wild symbol. The name of a constructor is indicated with ?. Thus, ?(void) requires the instantiator type to define a default constructor. Currently, it is not possible to specify the requirement of a copy constructor.
pattern-method-parameter-list --->>> ( --+---------------------------+---> ) | | +--+---> pattern-type ---+--> | | <------- COMMA -------+
#include<iostream.h>
using namespace ioSpace;
///////////////////////////////////////////////////////////////////////////////
// First we define two patterns. Type_Pattern specifies acceptable types for
// instantiating a template. Method_Pattern lists the methods required by the
// type instantiating a template.
// ------------------------------------------------------------------------- //
pattern template<T> Type_Pattern
T : int, long;
T : short*;
end;
pattern template<T> Method_Pattern
long fun(int, double);
int ?(long, short); //function name is wild
void operator+(int);
const T& ?(int); //function name is wild, return is template
operator int(void) cast; //conversion operator, with explicit cast
?(short) cast; //constructor with explicit cast
?(void); //default constructor
end;
///////////////////////////////////////////////////////////////////////////////
// This is a simple template showing how pattern specification is attached to
// a template parameter. Here, Type_Pattern is attached to U, and Method_Pattern
// specifies instantiator for template parameter V.
// ------------------------------------------------------------------------- //
template<type U : Type_Pattern, type V : Method_Pattern>
struct example
U i;
V d;
end;
///////////////////////////////////////////////////////////////////////////////
// The type patternType is used to instantiate the above template, in the second
// parameter, namely V. It therefore must contain all methods listed in pattern
// Method_Pattern.
// ------------------------------------------------------------------------- //
struct patternType
short s;
patternType(void);
patternType(short) cast;
long fun(int, double);
void operator+(int);
int wild(long, short);
const patternType& wild(int);
operator int(void) cast;
end;
patternType::patternType(void)
s = 0;
end;
patternType::patternType(short h)
s = h;
end;
long patternType::fun(int i, double d)
return i;
end;
void patternType::operator+(int n)
end;
int patternType::wild(long l, short h)
return int(l);
end;
const patternType& patternType::wild(int n)
return self;
end;
patternType::operator int(void)
return int(s);
end;
///////////////////////////////////////////////////////////////////////////////
entry void main(void)
output << "Hello World!\n";
example<short*, patternType> ext; // patterns are checked at instantiation
short j = 7;
ext.i = &j;
output << *ext.i << '\n';
output << "Good-bye World!\n";
end;
The output is as follows.
Hello World!
7
Good-bye World!
template-type-definition --->>> template-parameters-declaration --+--> class-definition ---+--> END --> SEMI-COLON | | +--> struct-definition --> | | +--> task-definition ---->
Remark. There is no syntactic differences in defining, say a class and a template class. However, note the following as an example.
In derivation, for class-definition, we see user-defined-type. The right
of user-defined-type includes instantiated-type. Now, when dealing with templates,
a previously defined template used in the definition of another template must
be instantiated with type names of template-parameter-id, shown in following rules.
template-parameters-declaration --->>> TEMPLATE --> LESS --> template-placeholders --> MORE --> template-placeholders --->>> --+--> TYPE --> template-parameter-id --+-----------------------------+--+--> | | | | | +--> COLON --> pattern-name --> | | | <------------------------------- COMMA ------------------------------+
Remark. template-parameter-id is an IDENTIFIER, which is specified as type within
the scope of template-parameters-declaration.
pattern-name is the name of a previously defined template pattern. A
template-parameter-id with a pattern can only be instantiated with types
that satisfy the conditions of the pattern.
We are going to define a template task, with hears() specification, named HearingTask. The method definitions will be given as an example for next rule: template-function-definition.
// Definition of template for type of first member of HearingTask
template<type U> struct firstMember U i; firstMember(U); end; template<type U> firstMember::firstMember(U n) i = n; end;
// Definition of template for type of second member of HearingTask
template<type U> struct secondMember U d; secondMember(U); end; template<type U> secondMember::secondMember(U b) d = b; end;
// Definition of template task: HearingTask
// Within the definition of HearingTask, the types firstMember<U> and secondMember<V> // are instantiations of template types firstMember and secondMember.
template<type U, type V> task HearingTask firstMember<U> ti; secondMember<V> di; hears(_SIGNAL_telling_1<firstMember<U>, secondMember<V> >, _SIGNAL_telling_2); void FirstHearHandler(void)<_SIGNAL_telling_1 : ti, di>; void SecondHearHandler(void)<_SIGNAL_telling_2>; public: HearingTask(U, V); end;
template-function-definition --->>> template-parameters-declaration --> function-definition
Remark. Inside function-definition, for a template-function-definition, template-parameter-id
are parsed as instantiators (previously-defined types). As in case of template-type-definition,
previously-defined template types are parsed as template instantiation.
The definition of methods for HearingTask of example for previous rule is given here.
// The constructor. U and V are parsed as previously defined types. firstMember<U> and // secondMember<V> are parsed as instantiations of previously defined template types. template<type U, type V> HearingTask::HearingTask(U u, V v) ti = firstMember<U>(u); di = secondMember<V>(v); end; // These are the handlers for signals for template task HearingTask. template<type U, type V> void HearingTask::FirstHearHandlerOne(void) // statements end; template<type U, type V> void HearingTask::SecondHearHandlerOne(void) // statements end;
template-typedef-definition --->>> TEMPLATE LESS TYPE --> template-parameter-id --> MORE --> typedef-definition
Remark. template-typedef-definition can only have a single template-parameter-id,
without a pattern. The template-parameter-id in the typedef-type is parsed
as a previously defined type.
template<type T> typedef myTypedef T[5]; // definition myTypedef<int>* intArrayPointer; // an instantiation as "int[5]*"
// Suppose you are defining a new template class that needs a degenerate // array pointer. First the typedef. template<type T> typedef degenerateArray T[]; // definition // Now, among class members, you include the following member. arrayPointer // is a degenerate array pointer on type T. degenerateArray<T>* arrayPointer;
Graphical User Interface entities are created with Graphic Maker Tool. The Tool generates a resource file for Linker, and an include file. The terms menubar, menu and canvas are Z++ reserved words that appear in the generated include files. All other terms, such as Button, Radiobutton, Checkbox are NOT reserved words.
The rules discussed here, though automatically generated by Tool, are Z++ language rules. The general idea is that, a menubar can only contain a list of menus. A menu contains a list of commands and submenus. Finally, a canvas can contain a menubar, and other entities like Combobox, Groupbox, Checkbox etc.
graphical-interface-definition --->>> +--> menu-definition -----+---> | | +--> menubar-definition --> | | +--> canvas-definition --->
Remark. menu-name, menubar-name, item-name and canvas-name in the following rules are IDENTIFIERs.
An interface-item is a control such as a TextArea, Button, List etc.
For a menu, the term command is not a reserved word, which is why it is not colored. The name for a command is a string like Open, Close, Exit Application, etc. The name for the menu itself is something like File, Edit, etc.
<--------------- SEMI-COLON <---------------+
| |
menu-definition --->>> menu --> menu-name --+--+--> command -------> command-name --+---+--> end ---> SEMI-COLON
| |
+-------> menu ---> menu-name ------->
menubar-definition --->>> menubar ---> menubar-name --+--> menu ---> menu-name --+--> end --> SEMI-COLON
| |
<------ SEMI-COLON <-------+
canvas-definition --->>> canvas ---> canvas-name --+----------------------------------+--+--> iterface-item ---> item-name --+--> end --> SEMI-COLON
| | | |
+---> menubar ---> menubar-name ---> <--------- SEMI-COLON <-------------+
Below is an example of a generated include file containing menus. Comments are manuaaly added for illustration.
menu File
command Open;
command Save;
command Exit;
end;
menu Select // This will be used as submenu below
command Bold;
command Underline;
end;
menu Edit
command Find;
menu Select; // This is the menu defined above, serving as submenu here
end;
menubar EditorMenu
menu File;
menu Edit;
end;
canvas Editor
menubar EditoMenu
TextArea Text;
end;
Below is an example of a generated canvas for a diaglog window.
canvas Personal
GroupBox Gender;
Label Last;
Label Name;
Button Done;
ComboBox Food;
TextArea LastName;
TextArea FirstName;
RadioButton Male;
RadioButton Female;
end;
instantiated-type --->>> scope-space --> type-name ---> LESS --+--> typedef-type --+--> MORE ---> | | <------ COMMA ------+
Remark. instantiated-type is the result of instantiating a template-type-definition. The type-name is the same IDENTIFIER used in template-type-definition. Since typedef-type can include instantiated-type, this is recursive.
Following is a complete program using nested template definitions. Notice the space between two consecutive >.
#include<iostream.h> using namespace ioSpace;
// member will be used to defined nested template
template<type U, type V> struct member U t; V s; member(U, V); end; template<type U, type V> member::member(U i, V j) t = i; s = j; end;
// owner has two nested template type members: m and n.
template<type U, type V> struct owner member<member<U, V>, V> m; member<U, member<U, V> > n; owner(member<member<U, V>, V>, member<U, member<U, V> >); member<U, V> leftOfFirst(void); member<U, V> rightOfSecond(void); end; template<type U, type V> owner::owner(member<member<U, V>, V> i, member<U, member<U, V> > j) m = i; n = j; end; template<type U, type V> member<U, V> owner::leftOfFirst(void) return m.t; end; template<type U, type V> member<U, V> owner::rightOfSecond(void) return n.s; end;
//---------------------------------------------------------------------------//
entry void main(void) output << "Hello World!\n";
owner<int, double> w(member<member<int, double>, double>(member<int, double>(4, 9.3), 77.47), member<int, member<int, double> >(47, member<int, double>(8, 8.1)) ); output << w.m.t.t << '\n'; // 4 output << w.m.t.s << '\n'; // 9.3 output << w.m.s << '\n'; // 77.47 output << w.n.t << '\n'; // 47 output << w.n.s.t << '\n'; // 8 output << w.n.s.s << '\n'; // 8.1 output << w.leftOfFirst().t << '\n'; // 4 output << w.rightOfSecond().s << '\n'; // 8.1
output << "Good-bye World!\n"; end;
The output of the program is:
Hello World!
4
9.3
77.47
47
8
8.1
4
8.1
Good-bye World!
The rule parameter-list defined in this section is used in rules for defining prototypes.
parameter-type --->>> --+------------+-> declaration-type ---> parameter-specifier | | +--> CONST --> parameter-specifier --->>> +----------+---+-----------+--> array-modifier | | | | +-+-> * -+-> +---> & ----> | | <------+ return-type --->>> parameter-type parameter --->>> parameter-type --+------------------------------------------------+--> | | +---> parameter-name ---+------------------------> | | +--> = --> initializer -->
Remark. When a parameter has an initializer, all parameters after that must also
have initializers. parameter-name is an IDENTIFIER.
parameter-list --->>> ( --+----------------------+---> ) | | +--+--> parameter --+--> | | <---- COMMA ----+
method-prototype --->>> +---> class-method ---+---> SEMI-COLON | | +---> task-method ---->
| | +---> frame-method ---> | | +---> module-method --> class-method --->>> +---> regular-prototype -------+--> | | +---> constructor-prototype ---> | | +---> destructor-prototype ----> | | +---> conversion-prototype ----> | | +---> friend-prototype --------> task-method --->>> +---> class-method --------+--> | | +---> idler-prototype -----> | | +---> handler-prototype ---> frame-method --->>> +---> class-method ---------+--> | | +---> instinct-prototype ---> module-method --->>> +---> native-class-module-method ---+--> | | +---> native-task-module-method ----> | | +---> foreign-module-method --------> native-class-module-method --->>> +---> class-method ---------+--> | | +---> external-prototype ---> native-task-module-method --->>> +---> task-method ----------+---> | | +---> external-prototype ---> foreign-module-method --->>> +---> class-method ---------+--> | | +---> external-prototype --->
regular-prototype --->>> method-head ---> method-tail ---> SEMI-COLON method-head --->>> return-type --+--> method-name --------------------+--> parameter-list ---> | | +---> OPERATOR --> operator-symbol -->
Remark. method-name is an IDENTIFIER for the name of the method being prototyped.
operator-symbol is the operator being overloaded. In general, this is just
the operator symbol like '+' or '+='. When overloading array indexing the
symbol is '[]'. For function call, the symbol is '()'.
There are two additional symbols that are only used for overloading.
Post-script operators ++ and -- use a different symbol for their prototype
and definition. However, the call to these overloaded operators uses the
normal symbols ++ or -- apearing after the object-name on which they are
invoked.
Post-script ++ uses '+@', and post-script -- uses '-@'.
method-tail --->>> +--+---------------+---> tail-specifiers ---> | | +---> CONST ----> tail-specifiers --->>> +------------------+--+----------------------------------+--> | | | | +--> throws-list --> +--> { --> constraint-list --> } -->
constructor-prototype --->>> constructor-prototype-head -+-----------+--+------------------+-> constructor-tail --> SEMI-COLON | | | | +--> CAST --> +--> throws-list --> constructor-prototype-head --->>> type-name ---> parameter-list ---> constructor-tail --->>> +------------------------------------------------------+--> | | +-> COLON -+---+--> member-name -+-> parameter-list ---> | | | | | +--> base-name ---> | | | <----------------- COMMA <-----------------+
Remark. base-name is an IDENTIFIER for name of a base used in derivation for definition of this type.
destructor-prototype --->>> ~ ---> type-name ---> ( --+-----------+--> ) --->
| |
+--> VOID -->
idler-prototype --->>> @ ---> type-name ---> ( --+-----------+--> ) --->
| |
+--> VOID -->
Remark. Idler is optional for a task. When a task has an idler, the idler is automatically invoked whenever task has no requests or signals to handle.
instinct-prototype --->>> $ ---> type-name ---> ( interfaceEventType ---> & ---> ) --->
Remark. The type interfaceEventType is defined in system include file "interface.h". The instinct method of a frame automatically receives events intended for the instance of the frame. Event is passed by-reference. See select-statement.
handler-prototype --->>> VOID ---> IDENTIFIER ---> ( -+----------+--> ) ---> handler-tail ---> SEMI-COLON | | +-> VOID -->
Remark. IDENTIFIER is the name of the handler.
handler-tail --->>> LESS ---> handler-signal ---> MORE ---> handler-signal --->>> +---> SIGNAL-LITERAL --+--> | | +---> ACCEPT-SIGNAL ---|
| | +---> handler-hear ----> handler-hear --->>> HEAR-SIGNAL --+------------------------------+---> | | +--> COLON -+-> member-name -+-> | | <--- COMMA <---+
Remark. All signals are defined in system include file "exception.h".
HEAR-SIGNAL is a literal of tellSignalType, or its extension.
conversion-prototype --->>> OPERATOR --> return-type --> ( --+----------+--> ) --> conversion-specifiers --> SEMI-COLON | | +-> VOID --> conversion-specifiers --->>> +--------------------------------+---> | | +---> CONST ---+-------------+---> | | +---> CAST --->
friend-prototype --->>> FRIEND ---> method-head ---> SEMI-COLON
external-prototype --->>> EXTERNAL ---> method-head ---> SEMI-COLON
Remark. A friend-prototype is not recorded as a method of the type being defined. It is just a global function that has same access to members of the type as its own methods do. In order to reach members of the type, a friend-prototype needs a parameter of the type it is friend with.
An external method does not have a user-defined definition. The Compiler generates the body (definition) of an external method.
throws-list --->>> THROWS ---> ( --+--> EXCEPTION-LITERAL ---+--> ) | | <--------- COMMA <--------+
Remark. EXCEPTION-LITERAL is a literal of exceptionEventType, or its extension. Exceptions are defined in system include file "exception.h".
constraint-list --->>> +--+--> constraint ---+--> | | <------------------+
constraint --->>> ( ---> boolean-expression ---> ) ---> violation-action ---> SEMI-COLON
violation-action --->>> +--> EXCEPTION-LITERAL --+--> | | +--------> trigger ------>
Remark. trigger is pure-prototype (defined below) of a none-public method.
pure-prototype --->>> +--> method-name ---------------------+--> prototype-parameter-list ---> | | +----> OPERATOR --> operator-symbol --> prototype-parameter-list --->>> ( --+---------------------------+---> ) | | +--+--> parameter-type --+--> | | <------ COMMA --------+
Remark. Unlike methods, global functions are not required to have a prototype
prior to their definition. In such cases, function-tail can appear at
the definition of global functions. However, when a prototype exists,
function-tail must only appear at prorotype.
When prototype and definition are separated, parameter initializers can
only appear in prototype, not the definition of function.
function-prototype --->>> function-prototype-declaration ---> SEMI-COLON function-prototype-declaration --->>> return-type --> IDENTIFIER --> parameter-list -+-------------------+-> | | +-> function-tail -->
Remark. Instead of IDENTIFIER for the name of function, you can use OPERATOR
followed by an operator symbol, for globally overloading that operator.
However, the Compiler will not allow globally overloading an operator
for fundamental types. At least one parameter must be of user-defined
type.
The compiler will also attempt to detect infinite recursion when the
same operator is used in its definition.
Globally overloading should only be used on user-defined types in cases where such overloading was not possible via method-overloading due to the order of definitions of those types.
function-tail --->>> +-------------------------------------------------------------------+--> throws-list --> | | +--> thread-specifier --+-------------------+--+-----------------+--> | | | | +--> accepts-list --> +--> hears-list -->
Remark. The order of accepts-list, hears-list and throws-list is not significant.
Note that only threads can have accepts-list and/or hears-list.
thread-specifier --->>> LESS ---> THREAD --+--------------------------------------+--> MORE | | +--> COLON --+--> DISENGAGE-SIGNAL --+-> | | <------- COMMA <--------+
Remark. DISENGAGE-SIGNAL is a literal of signalEventType, or its extension. Similarly, HEAR-SIGNAL is a literal of tellSignalType or its extension. Signals are defined in system include file "exception.h".
accepts-list --->>> ACCEPTS ---> ( --+--> ACCEPT-SIGNAL --+--> ) | | <------ COMMA <------+
hears-list --->>> HEARS ---> ( --+--> hear-element --+--> ) | | <------ COMMA <-----+
hear-element --->>> HEAR-SIGNAL --+-----------------------------------------------+--> | | +--> LESS -+-> declaration-type -+-> MORE --> | | <------- COMMA <------+
control-statement --->>> +---> selection-statement ---+---> SEMI-COLON | | +---> iteration-statement ---> | | +---> layer-statement -------> selection-statement --->>> +---> if-statement -----------+---> | | +---> switch-statement -------> | | +---> select-statement -------> iteration-statement --->>> +---> for-statement ----------+---> | | +---> do-statement -----------> | | +---> while-statement -------->
if-statement --->>> IF --> ( --> boolean-expression --> ) --> statement-block --> elsif-block --> else-block --> ENDIF --> elsif-block --->>> --+--------------------------------------------------------------------------+---> | | +--+--> ELSIF --> ( --> boolean-expression --> ) --> statement-block --+--> | | <--------------------------------------------------------------------+ else-block --->>> --+--------------------------------+--> | | +--> ELSE --> statement-block --->
switch-statement --->>> SWITCH --> ( --> switch_argument --> ) --+----------------------+--> switch-body --> ENDSWITCH --> | | +--> statement-block --> switch_argument --->>> expression-statement
Remark. The type of object resulting from switch_argument
must be, string,
enumeration, char or DISCRETE-NUMERIC.
switch-body --->>> case-list --+---------------+---> | | +--> else-leg --> case-list --->>> --+--> case-leg --+---> | | <---------------+ case-leg --->>> CASE ---> case-argument ---> COLON ---> statement-block ---> case-argument --->>> +---> single-argument -----+---> | | +---> sequence-argument ---> | | +---> range-argument ------> single-argument --->>> LITERAL
Remark. The type of LITERAL must match the type of switch_argument.
sequence-argument --->>> single-argument ---> COMMA --+--> single-argument --+---> | | <------- COMMA <-------+ range-argument --->>> single-argument ---> .. ---> single-argument --->
Remark. A range of values contains the start and end values, along with all
values in between.
Two consecutive dots (with no space in between) is the symbol for range.
else-leg --->>> ELSE ---> statement-block --->
for-statement --->>> FOR --> ( --> for-determinant --> ) --> statement-block --> ENDFOR -->
for-determinant --->>> --+-------------------------+--> SEMI-COLON --+-------------------------+--> SEMI-COLON --+---------------------+--> | | | | | | +--> for-initialization --> +--> boolean-expression --> +--> expression-leg -->
for-initialization --->>> +--+--> regular-for-declaration ---+---+---> ^ | | | | +--> constructor-declaration ---> | | | <--------------- COMMA ----------------+ regular-for-declaration --->>> declaration-type --+----------+---> object-name --+-------------------------+--> | | | | +-+-> * -+-> +--> = --> initializer ---> | | <------+
expression-leg --->>> --+--> expression-statement --+--> | | <---------- COMMA ----------+
Remark. expression-leg is a comma-separated sequence of expressions. The object
resulting from an expression-leg is the object resulting from the last
expression in the sequence.
do-statement --->>> DO ---> statement-block ---> ENDDO --+-------------------------------------+---> | | +--> ( --> boolean-expression --> ) -->
Remark. Without (boolean-expression), a do loop becomes an infinite loop.
Otherwise, a do loop will terminate when boolean-expression evaluates to TRUE.
while-statement --->>> WHILE --> ( --> boolean-expression --> ) --> statement-block --> ENDWHILE -->
A frame must have an instinct method for processing signals. Signals, such as mouse-click, for entities (button etc) of the canvas associated with the (instance of) frame are deliverd to its instinct method. The select-statement is the structure for handling and processing signals (events) passed to the instinct method.
The system include file "interface.h" defines graphical interface events. The enumeration type interfaceEventSignals lists events such as mouse-click.
When an event, such as a mouse-click is generated for a canvas, Z47 invokes the instinct method of the frame associated with that canvas and passes an instance of the following structure to it.
struct interfaceEventType
ushort x; // horizontal coordinate
ushort y; // vertical coordinate
ushort entity; // button-name, menu-item, menubar-name, etc.
interfaceEventSignals Event; // mouse-click, etc.
end;
Suppose the IDENTIFIER for the argument passed to the instinct method is "event". Then, select-argument in the rule below will be event.entity, which evaluates to an identifier such as "STOP", used as label for a BUTTON entity in the canvas. In this example the entity-name for a case will be "STOP".
select-statement --->>> SELECT --> ( --> select-argument --> ) ---> select-body --> ENDSELECT -->
select-body --->>> select-cases --+----------------+--->
| |
+--> else-leg --->
select-cases --->>> --+--> CASE --> entity-name --> COLON --> statement-block --+-->
| |
<---------------------------------------------------------+
Remark. select-argument and entity-name are explained just before the rules. An entity-name could be name (label) of a control (like a button, a list etc), or an IDENTIFIER for menu-item or a menu-bar.
First we use the GUI Maker and create a canvas. Once the canvas is made, we tell the GUI Maker to generate the following include file for the canvas.
// Person.h
// Generated by Z++ GUI Maker
canvas Personal
GroupBox Gender;
Label Last;
Label Name;
Button Done;
ComboBox Food;
TextArea LastName;
TextArea FirstName;
RadioButton Male;
RadioButton Female;
end;
The source file below is an example using the above canvas.
//Person.zpp
#include<interface.h>
using namespace interfaceSpace;
#include<exception.h>
using namespace exceptionSpace;
#include"Person.h" // Generated by GUI Maker
// Definition of frame
frame personFrame := Personal
public:
personFrame(void);
$personFrame(interfaceEventType&); // This is the instinct method
end;
// The constructor
personFrame::personFrame(void)
end;
// The instinct method is automatically invoked when there is
// an event associated with the canvas of the frame. The envent
// e is also passed to the instinct method.
personFrame::$personFrame(interfaceEventType& e)
switch(e.Event)
case _IES_Draw_Signal: // generated in main() via $PF
// Put a few items in the ComboBox Food of canvas Personal
Personal::Food + "Pizza";
Personal::Food + "Steak";
Personal::Food + "Rice and Chicken";
Personal::Food << 0;
return;
case _IES_Erase_Signal: // generated by "case Done"
$self; // This will erase the canvas
return;
// The signal is generated when user clicks an entity on canvas.
case _IES_Mouse_Click_Signal:
// For the given signal (_IES_Mouse_Click_Signal) the select statement
// processes the signal for the entity of canvas "e.entity"
select(e.entity)
case Male:
Personal::Food << 0;
case Female:
int index = 3; // cause an exception
layer<exceptionEventType>
Personal::Food << index;
handler
case _EXCEPTION_GUI_IndexOutOfRange:
index = 2; // repair the cause of exception
repeat; // and start over
endlayer;
case Done: // user clicked Done button
Personal <- _IES_Erase_Signal;
endselect;
endswitch;
end;
// The main() entry point
entry void main(void)
personFrame PF; //create an instance of frame
$PF; // draw the canvas (generates _IES_Draw_Signal)
end;
System exceptions are defined in system include file "exception.h". The type of system exceptions is exceptionEventType. User-defined exceptions are objtained by extending the system exceptions.
The layer statements catches explicitly. as well as implicitly raised exceptions, such as those raised by invariants.
For resuming after handling an exception see Resumption.
layer-statement --->>> layer-head --> layer-body --> handler-section ---> ENDLAYER --> SEMI-COLON
layer-head --->>> LAYER --> LESS --> exception-type --> MORE
Remark. The exception-type between < and > specifies the type of exceptions that the layer statement will handle in its handler section. The exception-type is either the system exceptions exceptionEventType or a user-defined extension of it.
layer-body --->>> statement-block
Remark. Exceptions occuring in layer-body are caught in the handler-section.
handler-section --->>> HANDLER --> case-list --+------------------+---> | | +--> else-leg -----> | | +--> elsall-leg --->
elsall-leg --->>> ELSALL --> statement-block
Remark. Literals for the case-list must be exception literals.
The else-leg will catch all exceptions of type exception-type specified in layer-head that do not appear in case-list.
The elsall-leg will catch all system/user-defined exceptions that do not appear in case-list.
The example defines three user-defined exceptions, and uses three nested layer statements.
#include<iostream.h> using namespace ioSpace; #include<exception.h> using namespace exceptionSpace;
// Extend system exceptions to user-defined types MyExceptions, YourExceptions and HisExceptions.
enum MyExceptions : exceptionEventType { _EXCEPTION_FirstUserException, _EXCEPTION_SecondUserException, _EXCEPTION_ThirdUserException, _EXCEPTION_FourthUserException }; enum YourExceptions : MyExceptions { _EXCEPTION_FifthUserException, _EXCEPTION_SixthUserException, _EXCEPTION_SeventhUserException, _EXCEPTION_EighthUserException }; enum HisExceptions : YourExceptions { _EXCEPTION_NinethUserException, _EXCEPTION_TenthUserException, _EXCEPTION_EleventhUserException, _EXCEPTION_TwelfthUserException };
// The main() entry point.
entry void main(void) throws(_EXCEPTION_TwelfthUserException)
output << "Hello World!\n"; layer<HisExceptions> output << "In HisExceptions layer.\n"; layer<YourExceptions> output << "In YourExceptions layer.\n"; layer<MyExceptions> output << "In MyExceptions layer, raising _EXCEPTION_TwelfthUserException.\n";
// Let us explicitly raise an exception.
raise(_EXCEPTION_TwelfthUserException); // The next output will not be reached because of above exception. output << "In MyExceptions layer, after raising _EXCEPTION_TwelfthUserException.\n"; handler case _EXCEPTION_FirstUserException: output << "In MyExceptions Handler.\n"; // The else leg will not be entered becasue else only refers to exceptions // of type MyExceptions, which does not include _EXCEPTION_TwelfthUserException. else output << "In MyExceptions else.\n"; endlayer; handler case _EXCEPTION_FirstUserException: output << "In YourExceptions Handler.\n"; // elsall refers to all exceptions in exceptionEventType, and its extensions. So, in this case, the // elsall leg will be entered. elsall output << "In YourExceptions elsall, handling _EXCEPTION_TwelfthUserException.\n"; endlayer; output << "In HisExceptions, _EXCEPTION_TwelfthUserException already handled.\n"; // _EXCEPTION_TwelfthUserException was already handled so the handler will not be entered. handler case _EXCEPTION_TwelfthUserException: output << "In HisExceptions Handler.\n"; endlayer; output << "Good-bye World!\n"; end;
The output of this example is as follows.
Hello World! In HisExceptions layer. In YourExceptions layer. In MyExceptions layer, raising _EXCEPTION_TwelfthUserException. In YourExceptions elsall, handling _EXCEPTION_TwelfthUserException. In HisExceptions, _EXCEPTION_TwelfthUserException already handled. Good-bye World!
The execution of an expression results in an object. In other words, an expression
always evaluates to an object.
Making a call may or may not result in an object. The function or method being
invoked may have a void return.
When an expression ends with a SEMI-COLON, the object resulting from that
expression is simply ignored.
expression-statement --->>> --+--> ( --> expression-statement --> ) ---> expression-statement-tail ---+--> | | +--> expression-statement ---> OPERATION ---> expression-statement -----> | | +--> plain-expression -------------------------------------------------->
Remark. OPERATION is a binary operator.
<------------------------------------------------+ | | expression-statement-tail --->>> --+--+--> BELONG-OPERATOR --> plain-method-call ---+---> | | +--> identifier-expression-tail -------------->
Remark. Not all paths of the rule expression-statement-tail are semantically correct. For instance, when identifier-expression-tail ends in a base and we turn around, the BELONG-OPERATOR can only be the RESOLVER. But then, one may incorrectly invoke a method on an object whose type does not define that method. Compiler is responsible for semantic analysis once the parser accepts the syntax.
plain-expression --->>> +--> LITERAL -------------------------+---> | | +--> identifier-expression -----------+ | | +---> constructor-expression ---------> | | +---> cast-expression ----------------> | | +---> new-expression -----------------> | | +---> size-expression ----------------> | | +---> operator-expression ------------> | | +---> call-expression ----------------> | | +---> conditional-expression --------->
| |
+---> signal-expression -------------->
| |
+---> hear-expression ---------------->
identifier-expression --->>> +------------------+--> IDENTIFIER ---+---------------------------------+---> | | | | +--> scope-space --> +--> identifier-expression-tail -->
Remark. The IDENTIFIER in the rule is name of an object, or pointer to an object.
identifier-expression-tail --->>> +--> identifier-member-tail ---+---> | | +--> identifier-base-tail -----> <--------------------------------------------------------------+ | | identifier-member-tail --->>> --+--+-----------------+--+--> MEMBER-OPERATOR --> member-name --+--+--------------------------------------+--> | | | | | +--> array-cell --> | +--> BASE-OPERATOR --> space-base --+--> | | <------------------------------- RESOLVER ------------------------------------+ <---------------------------------------------------------------------------------------------------------------------------------------------+ | | identifier-base-tail --->>> --+--+-----------------+--> BASE-OPERATOR ---> space-base --+--------------------------------------------------------------------------------+--+--> | | | | +--> array-cell --> +--> RESOLVER --> member-name --+---------------------------------------------+-->
| |
+--+--> MEMBER-OPERATOR --> member-name ---+--> | | <---------------------------------------+
Remark. identifier-member-tail begins with MEMBER-OPERATOR to reach a member, but
identifier-base-tail begins with BASE-OPERATOR. However, they both end by
reaching either a base or a member.
array-cell --->>> --+--> [ --> expression-statement --> ] ---+---> | | <----------------------------------------+ space-base --->>> --+------------------------------------------+--> base-name ---+--> | | | +---> space-name --+----> RESOLVER -----+--> | | | | | | <---- space-name <---+ | | | <---------------------- RESOLVER <---------------------------+
constructor-expression --->>> declaration-type ---> ( ---> argument-list ---> ) --->
argument-list --->>> ---+-----------------------+--> | | +--+--> argument ---+---> | | <--- COMMA <-----+
Remark. constructor-expression can be viewed as the literal representation of an
instance of a type. For instance, 'type-name(some-arg, 35, 7.8)' is an
instance of type-name, with its literal value shown via three arguments.
Similarly, int(4) is an object of type int with value of 4.
The special cases of full-cast can be done with simpler forms of casting,
the simple-cast and the pointer-cast, with identical semantics.
However, the full-cast can do more (examples follow):
1. Moving up/down in a derivation tree.
2. Casting away or adding const to reference return of conversion operator.
cast-expression --->>> +--> full-cast ------+---> | | +--> simple-cast ----> | | +--> pointer-cast ---> full-cast --->>> CAST ---> ( ---> full-cast-type ---> COMMA ---> expression-statement ---> ) ---> full-cast-type --->>> ---+--------------+--> typedef-type --+----------+--> | | | | +---> CONST ---> +---> & --->
1. Moving up/down in a derivation tree. First we need to define the following types (lefBase, rightBase, middle, derived)
// Definition of leftBase
struct lefBase int i; lefBase(int); end; lefBase::lefBase(int n) i = n; end;
// Definition of rightBase
struct rightBase double d; rightBase(double); end; rightBase::rightBase(double b) d = b; end;
// Definition of middle
struct middle : lefBase, rightBase short s; middle(short, int, double); end; middle::middle(short t, int n, double b) : lefBase(n), rightBase(b) s = t; end;
// Definition of derived
struct derived : middle string g; derived(string, short, int, double); end; derived::derived(string h, short t, int n, double b) : middle(t, n, b) g = h; end;
// Now the sample code. derived drv("Hello", 7, 47, 97.47);
// Since conversion operator cannot return a base, cast is useful in getting // a copy of base. lefBase lbs = cast(lefBase, drv); output << lbs.i << '\n'; // prints 47
// Without pointer, cannot move down to a derived type. Only up to a base type. //middle mdl = cast(middle, lbs); // error
// Pointers can move up or down. Moving up to a base. derived* drvp = new derived("Hello", 7, 47, 97.47); lefBase* lbsp = cast(lefBase*, drvp); output << lbsp->i << '\n'; // prints 47
// From that base, moving down to a derived. middle* mdlp = cast(middle*, lbsp); output << mdlp->s << '\n'; // prints 7
// And from there again up to the other base. rightBase* rbsp = cast(rightBase*, mdlp); output << rbsp->d << '\n'; // prints 97.47
Remark. Casting unrelated pointers via full-cast is identical to pointer-cast,
which is the same as C-casting.
2. Casting away or adding const to reference return of conversion operator. When conversion operator returns a const reference, you can cast away the const. For instance, let the following be a conversion operator for some type.
operator const member& (void) cast;
First, the cast specification does not allow implicit conversion. For that reason you will need to use explicit cast. Second, with or without the cast specification you can remove the const from the return of the conversion operator.
cast(member&, object);
The above will invoke the conversion operator on the object, and return a none-const reference to member.
On the other hand, if the return of conversion operator was not const, you could make it const.
cast(const member&, object);
The above will make the return const, even if conversion operator simply returns a reference (not constant).
simple-cast --->>> declaration-type ---> ( ---> expression-statement ---> ) --->
Remark. Unlike constructor-expression simple-cast takes exactly one argument. So
in this respect simple-cast is a special case of constructor-expression.
However, simple-cast is a conversion and goes in both directions. When
its argument provides a conversion operator, it will invoke that operator.
The expression 'int(4.7)' is a simple-cast which results in an object of
type int with literal value of 4. On the other hand, 'int(4)' is a
constructor-expression.
pointer-cast --->>> ( ---> pointer-cast-type ---> ) ---> expression-statement ---> pointer-cast-type --->>> ---+--------------+--> typedef-type --+--------------+---> | | | | +---> CONST ---> +--+--> * ---+-> | | <---------+
Remark. Here, pointer-cast-type must evaluate to a pointer type.
pointer-cast is C-style of casting for pointer types only. Thus, the object
resulting from expression-statement must be of pointer type. Also note
that a pointer of requested type is returned, pointing to the same address.
That means, the object pointed to is not changed.
size-expression --->>> SIZE --> ( --> size-arguments --> ) -->
size-arguments --->>> +--> expression-statement ---+-------------------------------------+--> | | | | +--> COMMA --> expression-statement --> | | +--> TYPE-ID ------------------------------------------------------> | | +--> canvas-name ---> RESOLVER ---> entity-name ------------------->
Remark. entity-name is an IDENTIFIER for a control
in canvas, such as a list or a combo-box.
TYPE-ID is name of a numeric fundamental-type (from char to double).
Exmaples below illustrate the use of size.
1. size ( expression-statement ). The argument must evaluate to a string object. The length of string is returned as an int object.
string s = "Hello"; int length = size(s);
2. size ( expression-statement , expression-statement ). The first argument is a dynamic array, and the second its dimension. The size of requested dimension is returned, as an int object. The first dimension is 0, second is 1, etc.
int m = 3, n = 5;
// Create a two-dimensional dynamic array of doubles, and initialize all cells // with 47.97.
double da[m][n](47.97); if (size(da, 1) > size(da, 0)) output << "second dimension is bigger\n"; endif;
3. size ( numeric-type ). For instance, size(long) is 8.
4. size ( canvas-name :: entity-name ). If entity is a list or a combo-box that belongs to the canvas, size() will return the number of elements in the entity.
new-expression --->>> NEW ---> typedef-type ---+----------------------------------+---> | | +--> ( ---> argument-list ---> ) -->
operator-expression --->>> ---+---------------------------+--> expression-statement ---+----------------------+---> | | | | +--+--> UNARY-OPERATOR --+--> +--> UNARY-OPERATOR ---> | | <---------------------+
Remark. Simple examples for operator-expression are "*p++" and "++*p", where
identifier p is the expression-statement.
call-expression --->>> call-statement ---+---------------------------------+---> | | +--> expression-statement-tail ---> call-statement --->>> +--> function-call ---+---> | | +--> method-call -----> function-call --->>> +--> regular-function-call --------+---> | | +--> instantiated-function-call ---> regular-function-call --->>> scope-space ---> function-name ---> ( ---> argument-list ---> ) --->
Remark. function-name is an IDENTIFIER for name of a previously defined function.
instantiated-function-call --->>> scope-space ---> function-name ---> instantiation-list ---> ( ---> argument-list ---> ) ---> instantiation-list --->>> LESS --+--> declaration-type ---+--> MORE ---> | | <-------- COMMA ---------+
Remark. instantiated-function-call corresponds to template-function-definition.
For an example of instantiated-function-call, see instantiated-function-type.
method-call --->>> +--> plain-method-call ------+---> | | +--> self-method-call -------> | | +--> regular-method-call ----> | | +--> object-method-call -----> plain-method-call --->>> +--> method-name --------------------+--> ( ---> argument-list ---> ) ---> | | +--> OPERATOR ---> operator-symbol --> self-method-call --->>> SELF --> DOT --> plain-method-call -->
Remark. plain-method-call is calling a method of the type, within another method
of the type, and is equivalent to self-method-call. Syntactically,
plain-method-call is same as regular-function-call.
regular-method-call --->>> identifier-expression ---> BELONG-OPERATOR ---> plain-method-call --->
Remark. When calling a method of a base, identifier-expression would be the name
of the base on which the method is called.
object-method-call --->>> IDENTIFIER ---> ( ---> argument-list ---> ) --->
Remark. IDENTIFIER is the object on which the overloaded () is called. The syntax
is same as regular-function-call, and is therefore not a new syntactic
construct. However, the IDENTIFIER here is the name of an instance of a
type, making the semnatics significantly different.
Note that, operator() can also be invoked as in regular-method-call, as shown below.
"objectName.operator()(arguments)" or "objectName->operator()(arguments)"
conditional-expression --->>> boolean-expression ---> QUESTION ---> expression-leg ---> COLON ---> expression-leg --->
Remark. The type of object resulting from the two expression-legs must be the same. When boolean-expression evalutes to True, the object resulting from the expression-leg of QUESTION will be the object for the conditional-expression. Otherwise, the object for the conditional-expression will be the one resulting from the expression-leg of COLON.
The object resulting from conditional-expression can be of any type, including dynamic array of structural types, so long as assignment is permissible for the type. For instance, task and mutex cannot be assigned.
action-statement --->>> +---> jump-statement ------+---> SEMI-COLON
| |
+---> delete-statement ---->
| |
+---> destroy-statement --->
| |
+---> raise-statement ----->
| |
+---> signal-statement ---->
| |
+---> tell-statement ------>
| |
+---> using-statement ----->
| |
+---> endusing-statement -->
delete-statement --->>> DELETE ---> identifier-expression -->
destroy-statement --->>> DESTROY ---> identifier-expression -->
Reamrk. A common member is initialized using common-name as in common-member-initialization. Delete and destroy also use common-name for their argument. Note that common-name is included in identifier-expression.
Remark. The identifier expression for argument of delete and destroy must evaluate to a pointer object.
Delete will raise exception when deleting a null pointer.
Destroy will ignore a null pointer. In addition, destroy applies delete recursively, as shown in example below.
// Example illustrates the recursive action of destroy.
#include<iostream.h>
using namespace ioSpace;
// Define a structure
struct value
int i;
value(int);
~value(void);
end;
value::value(int n) // constructor
i = n;
end;
value::~value(void) // destructor
output << "In value::~value(void)\n";
end;
// typedefs for declaring array object vip
typedef valuePointer value*;
typedef valuePointerPointer value**;
typedef ArrayValuePtr valuePointerPointer[3];
// typedef is for operand of new operator
typedef ArrayValuePtrPointer ArrayValuePtr*;
// The main() entry point
entry void main(void)
output << "Hello World!\n";
// sizes for a two dimensional dynamic array
int first = 3, second = 2;
// vip is a two dimensional dynamic array. Cells of vip are two-level
// pointers to ArrayValuePtr, which is a static array of two-level
// pointers to objects of type value.
ArrayValuePtr** vip[first][second];
// Initialize vip
for (int one = 0; one < first; one++)
for (int two = 0; two < second; two++)
vip[one][two] = new ArrayValuePtrPointer(new ArrayValuePtr);
for (int index = 0; index < 3; index++)
(**vip[one][two])[index] = new valuePointer(new value(777));
endfor;
endfor;
endfor;
// Having used vip, now we can delete all objects, recursively. The
// following will call the destructor of value 18 times as can be
// seen from the output of destructor.
output << "\nStart of destroy.\n\n";
destroy vip;
output << "\nGood-bye World!\n";
end;
The output of this example is as follows.
Hello World!
Start of destroy.
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
In value::~value(void)
Good-bye World!
raise-statement --->>> RAISE --> ( --> EXCEPTION-LITERAL --> ) -->
Remark. For an example of raising an exception see the example for layer-statement.
signal-statement --->>> SIGNAL ---> BACK-ARROW ---> EVENT-LITERAL -->
signal-expression --->>> SIGNAL ---> ? ---> EVENT-LITERAL -->
Signals. The kind of signals that can be generated via the signal statement are defined in system include file "exception.h", and are as follows.
signalEventType, universalEventType, threadEntireEventType, processEntireEventType.
Literals of signalEventType and universalEventType will be indicated as SIGNAL-LITERAL.
Catching signals of threadEntireEventType and processEntireEventType requires registeration via accepts() statement. Literals of these kind of signals will be indicated as ACCEPT-SIGNAL.
When referring to either kind of signal, a literal will be indicated by EVENT-LITERAL.
Remark. signal-expression is an expression evaluating to True or False. The expression evaluates to True only when the signal has arrived.
// The main thread and a global thread communicate via signals.
#include<iostream.h>
using namespace ioSpace;
#include<exception.h>
using namespace exceptionSpace;
// Make singals for communication between threads
enum userSignals : signalEventType {
_SIGNAL_FirstActionSignal,
_SIGNAL_SecondActionSignal,
_SIGNAL_TerminationSignal,
_SIGNAL_CompletionSignal
};
// Define a global thread
void globalThread(double d, string& s)<thread>
output << "Thread is waiting for action...\n";
// Wait until a signal arrives, and when it does perform the action
do
if (signal ? _SIGNAL_FirstActionSignal)
output << "Value of double is: " << d << '\n';
elsif (signal ? _SIGNAL_SecondActionSignal)
output << "The string is: " << s << '\n';
elsif (signal ? _SIGNAL_TerminationSignal)
output << "Terminating...\n";
break;
endif;
enddo;
// Tell main() we are done
signal <- _SIGNAL_CompletionSignal;
end;
// The main() entry point
entry void main(void)
output << "Hello World!\n";
string announcement = "All is well!";
output << "main(): starting the thread...\n";
// Start the global thread
globalThread(4.7, announcement);
// Tell thread to do action one
signal <- _SIGNAL_FirstActionSignal;
// Tell thread to do action two
signal <- _SIGNAL_SecondActionSignal;
// Tell thread to terminate
signal <- _SIGNAL_TerminationSignal;
// Wait until thread terminates
do enddo(signal ? _SIGNAL_CompletionSignal);
output << "\nGood-bye World!\n";
end;
The output of this example is as follows.
Hello World!
main(): starting the thread...
Thread is waiting for action...
Value of double is: 4.7
The string is: All is well!
Terminating...
Good-bye World!
Tell and Hear signaling is intended for distributed signaling with data transfer. The object hear-signal in following rules is of type tellSignalType defined in system header file "exception.h".
A process intending to catch a (tell) hear signal must have one of its threads register the signal via hears-list.
tell-statement --->>> TELL --> BACK-ARROW --> hear-signal --> tell-tail --->
Remark. tell-statement sends signals. Before the signal and its associated data are sent, local Z47 Processor informs the remote Z47 that it is going to send it the signal. If a process on the remote Z47 has registered the signal, it will accept the request. Otherwise the remote Z47 will reject the signal. If a signal is rejected, the local Z47 will raise the exception _EXCEPTION_SIGNAL_SignalNotRegistered.
tell-tail --->>> +-------------------+--+---------------------------+--+-----------------------------+-->
| | | | | |
+---> $ ---> URL ---> +---> COMMA ---> PROCESS ---> +--> COLON --+--> OBJECT --+-->
| |
<--- COMMA <--+
Remark. URL is the IP address of remote process. PROCESS is the name of process to receive the signal.
OBJECT is an IDENTIFIER for name of an object being sent along with the signal. The type of object must match the type indicated in hears-list. The order and type of objects must be same as those indicated in hear-list, the same way a function is called. In case of type mismatch the signal will not be sent and the local Z47 raises exception _EXCEPTION_SIGNAL_TellOperandTypeMismatch.
hear-expression --->>> HEAR ---> ? ---> hear-signal --+-----------------------------+-->
| |
+--> COLON --+--> OBJECT --+-->
| |
<--- COMMA <--+
Remark. hear-expression catches signals sent by a tell-statement. When hear-expression catches an arrived signal it evaluates to True. Otherwise, hear-expression evaluates to False.
OBJECT is an IDENTIFIER as explained in above remark for tell-tail.
This example consists of two source files: Tell.zpp runs on local node, and Hear.zpp runs on the remote node. The header file TellHear.h is included in both source files.
It is important that the program Hear.zpp starts as a remote process (Hear.zxe) before the local process Tell.zxe begins. The remote process must register the signals before it can catch them.
// TellHear.h
#include<iostream.h>
using namespace ioSpace;
#include<exception.h>
using namespace exceptionSpace;
// Make some plain signals
enum MyOwnSignals : signalEventType {
_SIGNAL_Thread_started,
_SIGNAL_Thread_ended
};
// Define a few tell/hear signals
enum MyTellSignals : tellSignalType {
_SIGNAL_telling_1,
_SIGNAL_telling_2,
_SIGNAL_telling_3,
_SIGNAL_telling_4,
_SIGNAL_telling_5,
_SIGNAL_telling_6,
_SIGNAL_telling_7
};
// Types of object to be sent along with signal
enum simpleEnum { // an enumeration
_SIMPLE_enum_1,
_SIMPLE_enum_2,
_SIMPLE_enum_3
};
struct argumentType // a simple structure
int n;
double d;
argumentType(void);
argumentType(int, double);
end;
argumentType::argumentType(void)
n = 2;
d = 8.9;
end;
argumentType::argumentType(int nn, double dd)
n = nn;
d = dd;
end;
struct imbeddedType // a structure with string/enumeration members
simpleEnum e;
string s;
imbeddedType(void);
imbeddedType(simpleEnum, string);
end;
imbeddedType::imbeddedType(void)
e = _SIMPLE_enum_1;
s = "I am member of imbeddedType";
end;
imbeddedType::imbeddedType(simpleEnum ee, string ss)
e = ee;
s = ss;
end;
The source for the local example.
//Tell.zpp
#include "TellHear.h"
string url = "192.168.1.64"; // The IP of remote Z47
// The main() entry point
entry void main(void)
output << "Teller: Hello World!\n";
// Declare objects for sending with signal
string s = "I am sent by teller.";
simpleEnum se = _SIMPLE_enum_2;
argumentType at(777, 22.33);
// Send signals
tell <- _SIGNAL_telling_3 $ url: at;
tell <- _SIGNAL_telling_4 $ url: se, s;
// Declare and set values for objects to send
se = _SIMPLE_enum_3;
imbeddedType it(se, "Teller string.");
// Send the signal
tell <- _SIGNAL_telling_5 $ url: it;
output << "Teller: Good-bye World!\n";
end;
The source for the remote example.
// Hear.zpp
#include "TellHear.h"
// The thread that registers to receive signals
void hearFun(void)<thread>
hears(
_SIGNAL_telling_3<argumentType>,
_SIGNAL_telling_4<simpleEnum, string>,
_SIGNAL_telling_5<imbeddedType>)
// Declare objects to receive data associated with signal _SIGNAL_telling_3.
argumentType at;
do enddo(hear ? _SIGNAL_telling_3 : at);
output << "(hearFun) Values received for _SIGNAL_telling_3 ...\n";
output << "argumentType.n is: " << at.n << '\n';
output << "argumentType.d is: " << at.d << '\n';
output.flush();
// Declare objects to receive data associated with signal _SIGNAL_telling_4.
simpleEnum se;
string s;
do enddo(hear ? _SIGNAL_telling_4 : se, s);
output << "(hearFun) Values received for _SIGNAL_telling_4 ...\n";
output << "simpleEnum is: " << [se] << '\n';
output << "string is: " << s << '\n';
output.flush();
// Declare objects to receive data associated with signal _SIGNAL_telling_5.
imbeddedType it;
do enddo(hear ? _SIGNAL_telling_5 : it);
output << "(hearFun) Values received for _SIGNAL_telling_5 ...\n";
output << "imbeddedType.e is: " << [it.e] << '\n';
output << "imbeddedType.s is: " << it.s << '\n';
output.flush();
// Inform main() we are done
signal <- _SIGNAL_Thread_ended;
end;
// The main() entry point
entry void main(void)
output << "Hearer: Hello World!\n";
hearFun(); // begin thread to register and catch signals
// Wait until thread informs us that it is done.
do enddo(signal ? _SIGNAL_Thread_ended);
output << "Hearer: Good-bye World!\n";
end;
Using statement can open an entire namespace, exporting all its public items. It can also be used for exporting specific items. Endusing closes an opened namespace, or ends the exportation of an item.
using-statement --->>> USING --+--> NAMESPACE ---> space-name ----------------+-->
| |
+--> space-name --> RESOLVER --> export-item -->
endusing-statement --->>> ENDUSING --+--> NAMESPACE ---> space-name ----------------+-->
| |
+--> space-name --> RESOLVER --> export-item -->
Remark. space-name is an IDENTIFIER for name of a previously defined namespace.
export-item --->>> +---> declaration-type ----+--->
| |
+---> typedef-name -------->
| |
+---> export-function ----->
| |
+---> function-pointer ---->
| |
+---> export-object ------->
Remark. Only public items that belong to a namespace can be eported. For instance declaration-type includes fundamental types, which do not belong to any namespace and therefore cannot be exported.
typedef-name is the IDENTIFIER in typedef-definition for the name of typedef.
function-pointer is IDENTIFIER for function-name in function-type-definition, as explained there. For instance, for regular-function-type this is just the name of the function-prototype.
export-object is an IDENTIFIER for an object declared in the namespace.
export-function --->>> function-name ---> prototype-parameter-list -->
Remark. Methods and global operators defined in a namespace cannot be exported. In order to export a function its pure prototype, as shown by above rule must be used because the name of the function may be overloaded. function-name is the IDENTIFIER used in the definition or the prototype of the funtion.
The following example illustrates exporting namespace items.
#include<iostream.h>
using namespace ioSpace;
// Define a namespace for exporting some instantiated items.
namespace templateSpace
template<type T> struct test
T t;
test(T);
T method(T);
end;
template<type T> test::test(T u)
t = u;
end;
template<type T> T test::method(T u)
return t + u;
end;
typedef dblTest test<double>; // typedef an instantiation
template<type T> T templateFun(T t)
return t + 7;
end;
type instantiatedFunPointer templateFun<int>;
endspace;
// Define a namespace for exporting various items.
namespace someSpace
int i = 55;
int fun(int n, double b)
return ++n;
end;
type int funPtrType(int, double); // define a function pointer
funPtrType funPtrObject = fun; // create an initialized instance
struct check
int i;
check(int);
int method(int);
end;
check::check(int n)
i = n;
end;
int check::method(int n)
return i += n;
end;
check checkInstance(45);
typedef CheckType check;
endspace;
// The main() emtry point.
entry void main(void)
output << "Hello World!\n";
// Exporting instantiated typedef
using templateSpace::dblTest;
dblTest DT(4.7);
output << DT.method(1.3) << '\n';
endusing templateSpace::dblTest;
// Exporting instantiated call
using templateSpace::instantiatedFunPointer;
output << instantiatedFunPointer(3) << '\n';
endusing templateSpace::instantiatedFunPointer;
// Exporting an instance of funcamental type
using someSpace::i;
output << i << '\n';
// Exporting a function
using someSpace::fun(int, double);
output << fun(i, 2.1) << '\n';
endusing someSpace::i;
endusing someSpace::fun(int, double);
// Exporting an instance of a user-defined type
using someSpace::checkInstance;
output << checkInstance.method(5) << '\n';
endusing someSpace::checkInstance;
// Exporting typedef
using someSpace::CheckType;
CheckType CT(47);
output << CT.method(50) << '\n';
endusing someSpace::CheckType;
// Exporting a function pointer
using someSpace::funPtrObject;
output << funPtrObject(7, 2.1) << '\n';
endusing someSpace::funPtrObject;
// Exporting a function, and a function pointer type
using someSpace::fun(int, double);
using someSpace::funPtrType;
funPtrType localFun = fun;
output << localFun(7, 2.1) << '\n';
endusing someSpace::fun(int, double);
endusing someSpace::funPtrType;
output << "\nGood-bye World!\n";
end;
The output of this example is as follows.
Hello World!
6
10
55
56
50
97
8
8
Good-bye World!
jump-statement --->>> +---> return-statement -----+--->
| |
+---> break-statement ------>
| |
+---> continue-statement --->
| |
+---> resume-statement ----->
| |
+---> repeat-statement ----->
return-statement --->>> RETURN --+----------------------------+--->
| |
+--> expression-statement --->
Remark. The return statement ends execution of a function/method and returns to caller.
break-statement --->>> BREAK --+-------------------------------------+--->
| |
+--> ( --> boolean-expression --> ) -->
Remark. The break statement without boolean-expression can only appear in an iteration-statement. It exits the iteration statement in which it appears and passes control to the statement following the iteration statement.
The break statement with boolean-expression will only execute during a debug execution. When the boolean expression becomes true, execution will stop, acting like a break point has been visited.
continue-statement --->>> CONTINUE
Remark. The continue statement can only appear in an iteration-statement. When continue is visited, control goes to the start of iteration statement, skipping all the statements within iteration statement that follow the continue statement. However, n case of for-loop, continue does not skip the third section, usually used for incremment or decrementing the loop-counter.
The resume and repeat statements can only appear in the handler section of a layer-statement. They return control to the body of layer-statement.
resume-statement --->>> RESUME
Remark. The resume statement returns control to the statement after the one that caused exception to occur.
repeat-statement --->>> REPEAT
Remark. The repeat statement return control to the statement that caused the exception.
// This example contains nested layers. The inner layer statement uses resume.
// The outer layer uses repeat for resumption.
#include<iostream.h>
using namespace ioSpace;
#include<exception.h>
using namespace exceptionSpace;
// A function for raising exception _EXCEPTION_DivisionByZero.
int globalFun(int f, int s)
f /= s; // raises exception when s == 0
return 47;
end;
// The main() entry point.
entry void main(void)
output << "Hello World!\n";
int i = 10;
int j = 0;
layer<exceptionEventType>
output << "Starting layer.\n";
int ret = 777;
// Next statement will cause _EXCEPTION_DivisionByZero to happen.
// Then, repeat will come back here and make the function call again.
ret = globalFun(i, j);
// Will not execute first round
output << "ret is: " << ret << '\n';
handler
case _EXCEPTION_DivisionByZero:
// This is a nested layer inside of a case leg.
// resume and repeat within each handler go to the body
// of the layer containing them.
int k;
layer<exceptionEventType>
k = 0;
i / k; // raises exception
// resume comes back here, skipping the above statement
output << "Succeeded in nested layer.\n";
handler
case _EXCEPTION_DivisionByZero:
output << "Caught _EXCEPTION_DivisionByZero in nested layer\n";
// We go back and resume at statement after the one that raised
// exception, i.e. after the statement "i / k".
resume;
endlayer;
output << "Caught _EXCEPTION_DivisionByZero\n";
// Repair cause of exception and repeat the call to globalFun()
// in the outer layer.
j = 5;
repeat;
endlayer;
output << "Good-bye World!\n";
end;
The output of this example is as follows.
Hello World!
Starting layer.
Caught _EXCEPTION_DivisionByZero in nested layer
Succeeded in nested layer.
Caught _EXCEPTION_DivisionByZero
ret is: 47
Good-bye World!
A Z++ program can simultaneously interact with multiple databases, and multiple users can interact with each database.
The system include file "Database.h" defines all needed types. For each database connection, an instance of databaseType and databaseUserType must be created. Z++ database statements use the instance of databaseUserType.
The following terminology is used with regard to database statements.
Table is name of a table in a database.
Field is name of a column in a Table.
Pure-Expression is a Z++ expression free of database Tables and Fields. In a database statement, a Pure-Expression must be enclosed between {}. The Compiler will replace a Pure-Expression with its resulting object before sending it to the database.
Consider a database query. The result of query is a set of rows in a Table. Now, think of a container class, such as a List Template Class. Ideally, we want the query to insert all the rows into an instantiation of the List.
The type of instantiator for List would be a class, with members that match the Fields of a row. We refer to the instantiator as the Catalyser. The instantiated instance of the List template is the Container object. The method of Container that we user to insert instances of Catalyser into the Container is the Filler-Method. Note that Catalyser is a type (an instantiator).
A Z++ query uses the following mapping-clause.
mapping-clause --->>> --+--> Table --> DOT --> Field ---> LESS ---> catalyser-member ---> MORE --+-->
| |
<----------------------------- COMMA <-----------------------------------+
Table and Field are IDENTIFIERS for items in the database, and "Table.Field" is a column in the table. catalyser-member is an IDENTIFIER for the name of a member of Catalyser. A mapping-clause tells the query which Fields of the row are mapped to which members of the Catalyser.
argument-clause --->>> LESS ---> database-object ---> COMMA ---> Catalyser ---> COLON --+--> Table --+--> MORE --->
| |
<-- COMMA <--+
Remark. When multiple Tables are used, query will be on the join of tables.
database-object is an instance of library class databaseUserType.
using-clause --->>> USING --> Container --> COMMA --> Filler-Method -->
Remark. The following, where and set clauses, are SQL language clauses used in Z++ statements.
where-clause --->>> WHERE ---> ( ---> database-expression ---> ) --->
Remark. database-expression is an SQL expression, which can contain Z++ Pure-Expressions.
set-clause --->>> SET ---+---> Field ---> = ---> value ---+-->
| |
<------------ COMMA <------------+
Remark. Value is a literal, or a Z++ Pure-Expression. Literals are NUMERIC-LITERAL, CHAR-LITERAL or STRING-LITERAL.
database-stament --->>> +---> database-select ---+-->
| |
+---> database-fetch ---->
| |
+---> database-free ----->
| |
+---> database-insert --->
| |
+---> database-update --->
| |
+---> database-remove --->
The database select statement performs a query and inserts the result of query into a container.
database-select --->>> DATABASESELECT ---> argument-clause ---> mapping-clause --+-------------------+--> using-clause -->
| |
+--> where-clause -->
Remark. The key word DATABASESELECT is spelled with a single upper-case letter "databaseSelect".
The following example illustrates the syntax and the terminology. The example uses a Postgres database.
// Include some needed system header files.
#include<Iterator.h> // For List template and Iterator
#include<iostream.h>
using namespace ioSpace;
#include<database.h> // For database library objects
using namespace databaseSpace;
#include<exception.h>
using namespace exceptionSpace;
// The following parameters need to be set for connecting to a Postgres Server.
string dbServerIP = "127.0.0.1"; // IP address of Server
int dbPort = 5432; // port number
string databaseName = "database-name"; // Database name
string userName = "your-user-name"; // User login name
string userPassWD = ""; // password if needed
// ------------------------------------------------------------------------- //
// We assume, for this example, that there is a table in the database called
// "users", which has six fields: Name, Last, height, Weight, Gender, Age.
// ------------------------------------------------------------------------- //
// We use this structure for mapping the six fields of the table mentioned
// above to members of a Z++ object. You can define any class with methods to
// do whatever you like. This is only an illustration.
// ------------------------------------------------------------------------- //
struct element
string first; // maps to Name
string last; // Last
float height; // height
double weight; // Weight
char sex; // Gender
short age; // Age
end;
///////////////////////////////////////////////////////////////////////////////
// Start of main() entry point.
// ------------------------------------------------------------------------- //
// _EXCEPTION_EmptyContainer may be raised by Template Library.
// ------------------------------------------------------------------------- //
entry void main(void) throws(_EXCEPTION_EmptyContainer)
using namespace ztlListIteratorSpace; //open List, Stack and Queue namespaces
List<element> elementList; //instantiate list template with type element (catalyser)
boolean executionFailure = False;
// Outermost exception layer for database connection etc.
layer<exceptionEventType>
databaseType Dbase(dbServerIP, dbPort, databaseName, "", _Database_Kind_Postgres);
databaseUserType Duser(Dbase, userName, userPassWD);
Iterator<element> I; // Define an Iterator
-elementList; // empty the container before select populates it
// Exception layer for databaseSelect statement.
layer<exceptionEventType>
// Duser was created above. All database statements use this object.
// element is the catalyser.
// users is the table in the database.
// "users.Name<first>" maps the field "Name" of table "users" to "first", and so on
// for the remaining fields.
// The where clause is a database clause.
// Finally, the using clause tells Z++ compiler to use the method "append" of container
// "elementList" to put the rows resulting from query into the container.
databaseSelect<Duser, element : users> // argument-clause
users.Name<first>, // start of mappig-clause
users.Last<last>,
users.height<height>,
users.Weight<weight>,
users.Gender<sex>,
users.Age<age> // end of mapping-clause
where (Weight < {2 * value}) // Z++ pure-expression enclosed by {}
using elementList, append; // using-clause
handler
case _EXCEPTION_DATABASE_SelectQueryFailed:
executionFailure = True;
case _EXCEPTION_DATABASE_InsufficientMemoryForQueryResult:
executionFailure = True;
case _EXCEPTION_DATABASE_FetchFailed:
executionFailure = True;
endlayer;
if (executionFailure)
output << "Select statement failed.\n";
else
for (I = elementList; I; I++)
output << "Person : ";
output << I.Current().first << ' ';
output << I.Current().last << ' ';
output << I.Current().height << ' ';
output << I.Current().weight << ' ';
output << I.Current().sex << ' ';
output << I.Current().age << '\n';
endfor;
endif;
handler // This is the handler section for outermost exception layer
case _EXCEPTION_DATABASE_UnsupportedDatabaseKind:
output << "Caught _EXCEPTION_DATABASE_UnsupportedDatabaseKind\n";
executionFailure = True;
case _EXCEPTION_DATABASE_LibraryInitializationFailed:
output << "Caught _EXCEPTION_DATABASE_LibraryInitializationFailed\n";
executionFailure = True;
case _EXCEPTION_DATABASE_ConnectionToServerFailed:
output << "Caught _EXCEPTION_DATABASE_ConnectionToServerFailed\n";
executionFailure = True;
endlayer;
if (executionFailure)
output << "Terminating program due to exception.\n";
else output << "Program ended succefully.\n";
endif;
-elementList; // empty the container for cleanup
end;
There are times we do not wish to get the entire result of a query into a container. Instead, we want to examine or process, say 10 rows at a time, and may decide to stop at some point and ignore the remaining rows. For instance, this is useful when using a small handheld device. In this case, we can tell select to populate the container with the first 10 rows, and then use the fetch statement to retrieve 10 rows at a time. Finally, at any point we can use the free statement to discard the remaining rows, if any.
The reason for using fetch is mainly the lack of space on a device, like a smart-phone. The result of a query needs to be downloaded on the device. Thus, getting the result of a query and populating a container are two distinct operations. In case of a handheld device, it is possible to use a Proxy for getting the result of the query, and then retrieving a certain number of rows at a time on a smaller device.
database-fetch --->>> DATABASEFETCH ---> argument-clause ---> mapping-clause ---> using-clause --->
Remark. The keyword DATABASEFETCH is spelled "databaseFetch".
The arguments and operands of fetch are the same as its associated select, except fetch does not need to use a where-clause.
databaseFetch is a boolean expression. It evaluates to False when there are no more rows to fetch. Otherwise it evaluates to True.
database-free ---> DATABASEFREE ---> LESS ---> database-object ---> MORE --->
Remark. The keyword DATABASEFREE is spelled "databaseFree".
database-object is an instance of library class databaseUserType, as used in argument-clause of database select and fetch.
In this example we use the same database table and fields as in the example for select statement. However, we use an Ingres database server and a Proxy. The Proxy is just the Z++ Internet Server.
// Include some needed header files.
#include<Iterator.h>
#include<iostream.h>
using namespace ioSpace;
#include<database.h>
using namespace databaseSpace;
#include<exception.h>
using namespace exceptionSpace;
// Set up parameters for connecting to an Ingres Server.
string dbServerIP = "127.0.0.1"; // Set to IP address of Database Server
int dbPort = 21064; // port number
string databaseName = "database-name"; // Database name
string userName = "your-user-name"; // login name
string userPassWD = ""; // password if needed
systemDatabaseKinds dbKind = _Database_Kind_Ingres;
string vnode = "vnode-name"; // name of virtual node
int FetchSize = 5; // number of rows to fetch
string ZppServerIP = "127.0.0.1"; // IP of Proxy (Z++ Internet Server)
// ------------------------------------------------------------------------- //
// The catalyser for instantiating container (the list elementList).
struct element
string first;
string last;
float height;
double weight;
short age;
char sex;
end;
///////////////////////////////////////////////////////////////////////////////
// The main() entry point.
// ------------------------------------------------------------------------- //
entry void main(void) throws(_EXCEPTION_EmptyContainer)
using namespace ztlListIteratorSpace; //open List, Stack and Queue namespaces
List<element> elementList; //instantiate list template with type element (catalyser).
// Note that we are passing a none-zero FetchSize. This affects select statement
// as explained below. Also since we are providing the last argument for proxy,
// the ZppServerIP, calls will go through a proxy.
databaseType Dbase(dbServerIP, dbPort, databaseName, vnode,
dbKind, FetchSize, ZppServerIP);
databaseUserType Duser(Dbase, userName, userPassWD); // session begins here
// When FetchSize is 0 (default), select statement gets the entire result of the
// query in one pass, and puts it into the container elementList using its method
// append. For any positive value, select will only get as many rows. The remaining
// rows must be gotten using fetch, as illustrated later below.
databaseSelect<Duser, element : users>
users.Name<first>,
users.Last<last>,
users.Age<age>,
users.Gender<sex>,
users.height<height>,
users.Weight<weight>
using elementList, append;
element et;
// Now, for each iteration of the do loop, we output the values we have gotten in
// the container. Note that, method Head() removes the object from the container
// elementList, and that operator+() gets the number of elements in the container.
do
output << "Printing fetched rows.\n\n";
while (+elementList > 0)
et = elementList.Head();
output << "Person : ";
output << et.first << ' ';
output << et.last << ' ';
output << et.age << ' ';
output << et.sex << ' ';
output << et.height << ' ';
output << et.weight << '\n';
endwhile;
// After emptying the container, we fetch the next set of rows. Fetch will get as
// many rows as the number FetchSize. When there is nothing to fetch, the fetch
// expression returns False.
// Fetch uses the exact same syntax and operand as its associated select.
// However, select can also have a where-clause, but not fetch.
if (!databaseFetch<Duser, element : users>
users.Name<first>,
users.Last<last>,
users.Age<age>,
users.Gender<sex>,
users.height<height>,
users.Weight<weight>
using elementList, append)
break;
endif;
enddo;
// Since we are repeating fetch until all data is retrieved, the following
// is not needed. However, if used, must only come after fetch, in particular
// when we do not intended to retrieve the remaining rows. Free simply discards
// the remaining rows.
databaseFree<Duser>;
end;
Remark. The mapping-clause only needs to map the fields used in the following statements. This also true for select statement. However, select normally uses more fields.
database-insert --->>> DATABASEINSERT ---> argument-clause ---> mapping-clause ---> USING ---> catalyser-instance --->
Remark. DATABASEINSERT is spelled "databaseInsert". catalyser-instance is explained in the example, below.
database-update --->>> DATABASEUPDATE ---> argument-clause ---> mapping-clause ---> set-clause --+-------------------+-->
| |
+--> where-clause -->
Remark. DATABASEUPDATE is spelled "databaseUpdate".
database-remove --->>> DATABASEREMOVE ---> argument-clause ---> mapping-clause --+--------------------+--->
| |
+--> where-clause --->
Remark. DATABASEREMOVE is spelled "databaseRemove". Without a where-clause nothing will be removed.
We use databaseSelect Example, and only illustrate the statements as they would appear there.
// In all three examples, elementList is an instantiation of List template
// using element (catalyser) as instantiator.
// I is an Iterator.
//////////// databaseInsert
for (I = elementList; I; I++)
layer<exceptionEventType>
// The using leg for insert is not the same using-clause of select. After "using" we need
// an object, an instance of element (the catalyser). "I.Current()" is the object in the
// container currently pointed to, by the Iterator I.
databaseInsert<Duser, element : users> // argument-clause
users.Name<first>, // mapping-clause
users.Last<last>,
users.height<height>,
users.Weight<weight>,
users.Gender<sex>,
users.Age<age>
using I.Current(); // using for insert
handler
case _EXCEPTION_DATABASE_InsertRequestFailed:
output << "Insertion of " << I.Current().first << " failed\n";
resume; // continue with next insert
endlayer;
endfor;
//////////// databaseUpdate
layer<exceptionEventType>
for (I = elementList; I; I++)
if (I.Current().first == "Cheeta")
// For mapping-clause, we only need to specify the fields we need for the database statement.
// The where-clause uses pure-expression (a Z++ expression enclosed with {}).
databaseUpdate<Duser, element : users> // argument-clause
users.Name<first>, // mapping-clause
users.Last<last>
set Name = "Jack", Last = "Jackson" // set-clause
where (Name == {I.Current().first}); // where-clause
endif;
endfor;
handler
case _EXCEPTION_DATABASE_UpdateRequestFailed:
output << "Update of " << I.Current().first << " failed\n";
resume; // continue with next update
endlayer;
//////////// databaseRemove
layer<exceptionEventType>
for (I = elementList; I; I++)
// The mapping-clause has only one field. The where-clause uses pure-expression, enclosed with {}.
if (I.Current().first == "Tarzan" ||
I.Current().first == "Jane" ||
I.Current().first == "Jack")
databaseRemove<Duser, element : users> // argument-clause
users.Name<first> // mapping-clause
where (Name == {I.Current().first}); // where-clasue
endif;
endfor;
handler
case _EXCEPTION_DATABASE_RemoveRequestFailed:
output << "Removal of " << I.Current().first << " failed\n";
resume; // continue with next delete
endlayer;
This section illustrates various categories of Z++ operators, their associativity and precedence.
The rules of associativity and precedence simplify expressions by reducing the use of parentheses. Precedence applies to the order of evaluation between two different operators, while associativity applies to consecutive occurrences of same operator.
Two different operators of same precedence are evaluated from left to right, except assignment operators.
Associativity implies that the order of evaluation of several occurrences of the same operator does not change the result. For instance,
a + (b + c) is the same as (a + b) + c.
However, we use the term associativity to imply that several consecutive occurrences of an operator can appear in an expression. Furthermore, in order to indicate the order of evaluation taken by the compiler, we say an operator is left-associative or right-associative.
All associative operators are left-associative except assignment operators, which are right-associative.
Consider the following example.
int i = 5, j = 7, k = 11;
j = k = i; // all objects are set to 5, the value of i
The same result could be obtained by the following declaration.
int i = j = k = 5; // all are set to 5
Binary assignment operators, like +=, are also right-associative, discussed further below.
The following links take you to subsections.
Unary Operators () [] and BELONG-OPERATORS.
Enumeration Bracket Function.
Collection Bracket Function.
Unary Operators.
Arithmetic Operators.
Bitwise Operators.
Relational Operators.
Logical Operators.
Assignment Operators.
Pointer Operators.
Graphic Operators.
1. Unary Operators () and []. and BELONG-OPERATORs, have the highest precedence.
Consider a function call, fun(m, n). First, the expressions m and n are evaluated to reach objects for passing to the call. Once this has been done, only then the operator () is applied to fun (the call is made). Thus, () is a (complex) unary operator.
Similarly, when reaching a cell of an array, say X[n+1], first the expression n+1 is evaluated, and only then the operator [] is applied to the array X. Thus, operator [] is also a (complex) unary operator.
Both of these operators are postfix, appearing on the right of the object to which they are applied. When both operators are applied to the same object, the order of evaluation is from left to right. Also, when the same operator is applied two or more times, the order of evaluation is from left to right. In case of array we usually envisage a multidimensional array, rather than order of evaluation of [].
// Example
#include<iostream.h>
using namespace ioSpace;
// Define a structure with operator() overloaded.
struct CallOverload
int n;
double d;
CallOverload(int, double);
int operator()(int);
double operator()(double);
end;
CallOverload::CallOverload(int t, double b)
n = t;
d = b;
end;
int CallOverload::operator()(int t)
return n+t;
end;
double CallOverload::operator()(double b)
return d + b;
end;
// The main() entry point.
entry void main(void)
output << "Hello World!\n";
int IntArg = 3;
double DoubleArg = 1.3;
// Declare an array of CallOverload.
CallOverload CO[3](47, 7.97);
// First array operator [] is evaluated, then operator () is applied to the cell.
output << CO[1](IntArg) << '\n'; // prints 50
output << CO[0](DoubleArg) << '\n'; // prints 9.27
output << "Good-bye World!\n";
end;
1.1. Other meanings of the operator [].
Operator [] is internally overloaded for other purposes, which we illustrate here. These functions of operator [] are referred to as the bracket function.
1.1.A. The enumeration bracket function, and double-bracket function.
The bracket function applied to an enumeration object evaluates to its integer value.
The bracket function applied to an enumeration type evaluates to an enumeration object with the lowest value.
The double-bracket function applied to an enumeration type evaluates to an enumeration object with the highest value.
Example: Enumeration Bracket Functions
#include<iostream.h>
using namespace ioSpace;
//---------------------------------------------------------------------------//
enum EnumTest {_First = 3, _Second = 5, _Third = 7, _Fourth = 11, _Fifth = 13};
///////////////////////////////////////////////////////////////////////////////
entry void main(void)
output << "Hello World!\n";
// et is initialized with _First, the value of object returned by [EnumTest].
// [[EnumTest]] evaluates to an object with value _Fifth.
// et++ moves to the successor.
// [et] is the integer value of the enumeration object et.
for (EnumTest et = [EnumTest]; et <= [[EnumTest]]; et++)
output << [et] << '\n';
endfor;
output << "\nGood-bye World!\n";
end;
The output of this example is as follows.
Hello World!
3
5
7
11
13
Good-bye World!
1.1.B. The collection bracket function, and collection index.
For a collection object Col, the bracket function [Col] evaluates to a reference to the tag object of collection. This allows changing the value of collection tag (when not specified protected).
The operator [] is also used as collection index, the same way it is used for array index. For a collection object Col, "Col[tag_value]" evaluates to the index-value associated with the ENUM-LITERAL "tag_value" of collection tag.
For an example of these uses of operator [] see Collection-Example, at the definition of method:
"void mostFiguresType::PrintAtTag(void)"
1.1.C. BELONG-OPERATORS.
Belong operators, like operators () and [], are immediately applied to the object on their left.
Consider the expression "X[...](...)->m", where ... is a placehoder for arguments.
First, [...] is applied to object X, yeilding the object "X[...]".
Next, operator (...) is applied to the object "X[...]", returning the object "X[...](...)".
Finally, operator -> is applied to the returned object to reach the member m.
The expression "*p.m" is evaluated by first reaching the member m, and then applying
the operator * to the member m. If instead, you want to apply operator * to p, you need
to use parentheses: "(*p).m", which is equivalent to "p->m".
2. Unary operators have next highest precedence.
Unary operators, with one exception, appear in front of object they are applied to, to which we shall refer as prefix notation. When several unary operators appear in prefix notation, the order of application is from object to its left. That is, first the operator closest to object is applied, then the next operator to its left is applied to the resulting object, etc.
Operators ++ and -- also have a postfix notation (to the right of object). The postfix notation has a special meaning. The postfix operator ++ and -- are applied after the object has been used in the evaluation of the expression in which it appears, or assignment has been done. However, note that postfix ++ and -- are still applied to the object on their left. For instanc, "*p++" means that ++ is later applied to p, not to "*p". That is not the same thing as "(*p)++", which will apply ++ to "*p".
The meaning of postfix ++ and -- is retained when applied to arrays, and when these operators are overloaded. These will be illustrated by examples.
Below is the list of unary operators and their meaning. Only ++ and -- change their operand. All other operators return the result of applying the operator without changing their operand.
Operators ++ and -- increment/decrement the value of their operand.
For discrete numeric objects, ++ adds one to its operand, and -- subtracts one from its operand.
For pointer operands, ++ moves pointer forward and -- moves it backwards. The distance moved is the size of object pointed to.
For enumeration types, ++ is moves the value of object to its successor, and -- moves its value to its predecessor.
Remark. In general, by "returning a value" we mean " returning an object with given literal value".
The following operators do not change the value of their operand.
Operator ! is logical operator returning the negation of its boolean operand.
Operator * returns the object pointed to by its operand.
Operator & returns the address of its operand (returns a pointer object pointing to its operand).
Operator ~ returns the bit-inverted values of its operand.
Operator - returns the negative value of its operand.
Operator + returns the value of its operand. In general, this operator does nothing.
For numeric literals the + or - are taken as the sign of the literal value. For instance, -5 is just that, negative 5.
// Example Pointers
#include<iostream.h>
using namespace ioSpace;
entry void main(void)
output << "Hello World!\n";
// Declare two arrays of integers.
// by default all cells are initialized to 0.
int a[5];
int b[5];
// Change values of cells of array b.
b[0] = 5;
b[1] = 12;
b[2] = 17;
b[3] = 21;
b[4] = 33;
// Declare two pointers pointing to first cell of arrays.
int* p = &a[0];
int* q = &b[0];
p--; // move p backwards
// Illustrating "*++p = *q++".
// First pointer p is moved forward (incremented).
// Then, * is applied to pointer (++p) reaching a cell of array a.
// Next, * is applied to pointer q reaching a cell of array b.
// The assignment is performed, using the value (*q).
// Finally, ++ moves pointer q forward.
for (int i = 0; i < 5; i++)
*++p = *q++;
endfor;
for (int i = 0; i < 5; i++)
output << a[i] << '\n'; // prints the values of array b.
endfor;
// Reset the values of p and q.
p = &a[0];
q = &b[0];
for (int i = 0; i < 5; i++)
a[i] = 0; // make all cells of a 0
endfor;
// Illustrating "*p++ = *q++".
// First * is applied to p, reaching a cell of array a.
// Next, * is applied to pointer q reaching a cell of array b.
// The assignment is performed, before applying ++ on either side.
// Finally, ++ is applied to pointers p and q.
for (int i = 0; i < 5; i++)
*p++ = *q++;
endfor;
for (int i = 0; i < 5; i++)
output << a[i] << '\n'; // prints the values of array b.
endfor;
output << "Good-bye World!\n";
end;
Below is a more complex example.
// Example Overloading and Arrays of structures.
//---------------------------------------------------------------------------//
// Keep in mind that when an overloaded operator is applied to an array of
// structures (class/struct, etc), it always returns an array object with
// the operator applied to each cell. Invoking a method on an array of
// structures works the same way.
#include<iostream.h>
using namespace ioSpace;
//---------------------------------------------------------------------------//
struct PostInc
int n;
PostInc(int);
int operator-(void);
int operator+@(void); // overloading postfix ++
PostInc& operator+(const PostInc&);
end;
PostInc::PostInc(int t)
n = t;
end;
int PostInc::operator-(void)
return n = -n;
end;
// Overloaded postfix ++. When applying the operator to an instance
// use the ordinary ++ token.
int PostInc::operator+@(void)
return ++n;
end;
PostInc& PostInc::operator+(const PostInc& e)
n += e.n;
return self;
end;
///////////////////////////////////////////////////////////////////////////////
// The entry point main().
entry void main(void)
output << "Hello World!\n";
output << "\nStatic array of struct ...\n\n";
PostInc PIA[3];
for (int i = 0; i < 3; i++)
PIA[i].n = 5 * i;
endfor;
// For all cells of array PIACopy, the member n of PostInc will be 0.
PostInc PIACopy[3];
// First, -PIA makes an array with member n of cells becomes 0, -5, -10.
// Next, -PIA is copied to PIACopty.
// Finally, the postfix ++ acts on original PIA, incrementing the member n: 1, 6, 11.
PIACopy = -PIA++;
for (int i = 0; i < 3; i++)
output << PIA[i].n << '\n';
output << PIACopy[i].n << '\n';
endfor;
output << "\nDynamic array of structure ...\n\n";
int dim = 3;
PostInc DIA[dim];
PostInc DIA2[dim](13); // initializes n == 13 for all cells.
// size(DIA, 0) evaluates to the size of first (0-th) dimension of dynamic array
// DIA, which is this case is 3.
for (int i = 0; i < size(DIA, 0); i++)
DIA[i].n = 5 * i;
endfor;
// First -DIA returns an array which is used as argument to the overloaded
// operator +. Finally, postfix ++ is applied to the original DIA.
DIA2 + -DIA++;
for (int i = 0; i < size(DIA, 0); i++)
output << DIA[i].n << '\n'; // 1, 6, 11
output << DIA2[i].n << '\n'; // 13, 8, 3
endfor;
output << "\nGood-bye World!\n";
end;
The output of this example is as follows.
Hello World!
Static array of struct ...
1
0
6
-5
11
-10
Dynamic array of structure ...
1
13
6
8
11
3
Good-bye World!
3. Arithmetic Operators have the next highest precedence.
Arithmetic operators are as follows.
+ Addition
- Subtraction
* Multiplication
/ Division
% Remainder (Modulo)
*^ Power (raising to a power)
Power has the highest precedence among arithmetic operators.
Operators Multiplication, Division and Remainder have the next highest precedence.
Addition and Subtraction have the lowest precedence.
For instance, "a + b * c *^ d" means "a + (b * (c *^ d))".
First Power is evaluated.
The result is used in evaluating multiplication.
Then, the result is used in evaluating addition, as illustrated below.
double a = 5, b = 4, c = 3, d = 2;
double e = a + b * c *^ d;
output << e << '\n'; // prints 41
Remark. For numeric types, the type of right operand of an arithmetic operator is coerced to the type of its left operand before the operation is carried out.
The output of following example is 5.
When evaluating b, d is coerced to int (of value 2).
Note that d is not changed as a result of coercion.
int i = 10;
double d = 2.5;
double b = i / d;
output << b << '\n';
4. Bitwise (arithmetic) operators have the next highest precendence.
Operands of these operators must be of discrete numeric types. Note that char and uchar are among discrete numeric types. Bitwise operators are as follows.
& And
| Or
^ Exclusive Or
<< Shift-left
>> Shift-right
Shift operators have higher precedence over the other three operator. In an expression, operators And, Or and Exclusive Or are evaluated from left to right relative to one another (they have equal level of precedence).
a << b & c means (a << b) & c
a | b >> c means a | (b >> c)
5. Relational operators have the next highest precedence.
Relational operators are binary and of equal precedence. They are as follows.
== Equal
!= Inequal
< Smaller (Less than)
<= Smaller or Equal
> Greater (More than)
>= Greater or Equal
As an example: a ^ b < c >> d + e means (a ^ b) < (c >> (d + e))
5.1. Chaining relational operators.
Relational operators can be chained. Less/Greater than must be in same direction in a chain.
a < b < c means (a < b) && (b < c)
a >= b == c > d means (a >= b) && (b == c) && (c > d)
6. Logical operators have next highest precedence.
Remark. The logical operator ! (negation) is unary, with same high precedence as unary operators.
The operands for logical operators must be of type boolean. The operators are as follows.
! Negation
&& (Short) And
|| (Short) OR
<> Long And
^^ Exclusive OR (is long logical operator)
>< Long Or
Remark. The short logical operators do not necessarily evaluate all their operands. For instance, when X, Y and Z are expressions involving function calls, and X evaluates to false in "X && Y && Z", the calls in Y and Z will not be made. However, the operands of the other three long logical operator are evaluated (e.g. function call are made) before applying the operators.
Operator && has the highest precedence. Operator || has next highest precedence.
Operators <> and ^^ have equal precedence after ||. Operator >< has the lowest precedence.
Example: Logical, relatinal operator precedence.
#include<iostream.h>
using namespace ioSpace;
//---------------------------------------------------------------------------//
int fun(int n)
output << "In fun, n is: " << n << '\n';
return n;
end;
///////////////////////////////////////////////////////////////////////////////
entry void main(void)
output << "Hello World!\n";
//---------------------------------------------------------------------------//
int a = 2, b = 3;
// In boolean expression of if, logical operators have the lowest precedence.
// Short and && has higher precedence over the long and <>.
// So we begin with operator &&. Its left operand is "a + b > a * b".
// Relational operator > has higher precedence over of &&.
// Since operators + and * have higher precedence over > they are computed first.
// The expression "a + b > a * b" is false. So, && does not need to compute its
// right operand. Thus, the function fun() is not called, nor b is incremented.
// Now we have the left operand for long and <>, which is false.
// However, long and does compute both its operands, even though is this case
// the overall result is false. So, at the end a will be incremented.
if (a + b > a * b && a < fun(b++) <> a++ == b)
output << "Was true\n";
else
output << a << '\n';
output << b << '\n';
endif;
//---------------------------------------------------------------------------//
output << "\nGood-bye World!\n";
end;
The output of this example is as follows.
Hello World!
3
3
Good-bye World!
7. Binary assignment operators have the lowest precedence, just above assignment itself.
The assignment operator = is right-associative and has the very lowest precedence. Binary assignment operators have higher precedence over the assignment only. All binary assignment operators are of equal precedence, and are evaluated from right-to-left. Below is the list of binary assignment operators.
Arithmetic operators:
+=
-=
*=
/=
%=
*^=
Bitwise operators
&=
|=
^=
<<=
>>=
~=
All binary assignment operators, except ~=, use both their operands in computing the result. The result is always assigned to the left operand. That is, bianry assignment operators change their left operand.
Operator ~= only inverts bits of its right operand, and assigns that to its left operand. It is equivalent to "a = ~a". Note that in contrast, operator -= will perform a subtraction using both of its operands before assigning the result to its left operand.
Example: binary assignment operators
#include<iostream.h>
using namespace ioSpace;
///////////////////////////////////////////////////////////////////////////////
entry void main(void)
output << "Hello World!\n";
int a = 2, b = 3, c = 4, d = 5;
// First "c + d" is done, getting 9.
// Then operator += is done making b == 12
// Finally, operator *= is done making a == 24.
a *= b += c + d;
output << "a is: " << a << '\n';
output << "b is: " << b << '\n';
output << "c is: " << c << '\n';
output << "d is: " << d << '\n';
output << "\nGood-bye World!\n";
end;
The output of this example is as follows.
Hello World!
a is: 24
b is: 12
c is: 4
d is: 5
Good-bye World!
The following example may be more helpful. Suppose we have the following declaration.
int n, a = 2, b = 4, c = 5, d = 3, e = 8;
Then, the following statement:
n = a += b * c += a + d *= e / a;
is computed as if parentheses were inserted as follows:
n = (a += ((b * c) += ((a + d) *= (e / a))));
If we print the values of objects, we find:
output << "a is: " << a << '\n'; // 42
output << "b is: " << b << '\n'; // 4
output << "c is: " << c << '\n'; // 5
output << "d is: " << d << '\n'; // 3
output << "e is: " << e << '\n'; // 8
output << "n is: " << n << '\n'; // 42
So, first (b * c), (a + d) and (e / a) are computed, and their results are stored in termporaries created by the Compiler. Then, assignment operators are done, from right-to-left.
Pointers can be compared using all relational operators. In addition, following operators can be used on pointers.
++
--
+=
-=
+ // binary add
- // binary subtract
= // assignment
* // unary prefix, dereference pointer
& // unary prefix, take address of pointer
The right operand of bnary operators must be of discrete numeric type. The amount by which a pointer is moved forward or backward is the product of right-operand and the size of object to which the pointer is pointing.
The only literal value that can be assigned directly to a pointer is 0 (zero). Otherwise, the address operator provides pointer literal values.
int n = 5;
int* p = &n; // &n is a pointer literal value
output << *p << '\n'; // prints 5
Operators simplify the notation for general actions such as drawing/erasing graphic entities. At Instinc Method Select, graphic events (signals), and other graphic-related concepts are illustrated. Here, we discuss operators for peforming graphic operations.
The labels (names) of entities in a canvas can be used as cases for select statement, only. For instance, suppose in a canvas named "People" there is a button named "STOP". The string STOP is used as a case label for select statement, but when applying an operator to that same button, it must be identified with its canvas: "People::STOP".
Similarly, menus and menubars must be identified with their canvas. To reach submenus and/or menu cammands, use the RESOLVER, recursively. For instance, "People::PeopleBar::File::Open", where PeopleBar is the menubar in canvas, File is the name of a menu item, and Open is a menu-command.
Below is the rule illustrated above. Note that operators can also be applied to menubar, menu and canvas itself. Entities in a canvas other than menubar and menu are usually called "control", e.g. a Checkbox is a control.
On the right-hand-side of the rule, all terms with suffix of "name" are IDENTIFIERS, except command-name, which is a string (may include spaces, etc.).
gui-entity --->>> canvas-name --+--------------------------------------------------------------------------------------------------------+-->
| |
+--> RESOLVER --+--> control-name --+-----------------------------------+-------------------------------->
| | | |
+--> menubar-name --+--+--> RESOLVER --> menu-name --+--+--> RESOLVER --> command-name -->
| |
<-----------------------------+
Unary Prefix Operators
9.1. Draw/Erase operator $
This is the only operator that can also be applied to the frame. When an instance of a frame is declared, it is created just like an instance of a class is declared, and nothing else happens. Applying operator $ to the instance,draws the canvas of the frame and enters gui-input-mode. In gui-input-mode, Z47 looks for user actions such as mouse-click, and delivers them to the instinct method of the frame. See the example for Instinct Method Select.
Inside the instinct method, $self will erase the canvas and end gui-input-mode for the frame. Note that the self refers to the instance of frame.
For all other cases "$gui-entity" is a toggle action. It draw gui-entity, or if already drawn, it will erase it.
9.2. Enable/Disable operator !
A disabled gui-entity is greyed out, and does not respond to mouse-click, etc. The statement "!gui-entity" is a toggle action for enbling and disabling a gui-entity.
9.3. Focus operator @
Events (signals) are delivered to the gui-entity whih currently has the focus. When clicking a control it automatically gets focus. The statement "@gui-entity" sets focus to the gui-entity.
9.4. Check operators ? ^ &
These operators return a boolean.
"?gui-entity" evaluates to true if gui-entity is drawn (showing), otherwise it evaluates to false.
"^gui-entity" evaluates to true if gui-entity is enabled, otherwise evaluates to false.
"&gui-entity" evaluates to true if gui-entity has focus, otherwise evaluates to false.
9.5. Clear operator ~
For a Field (test area) ~gui-entity clears the contents. For List and ComboBox it deletes all elements making them empty.
Binary Operators
9.6. Signal operators BACK-ARROW and double back-arrow
Suppose "canvas-name" is a canvas associated with some frame. The statement
canvas-name <- some-event;
sends the signal some-event to the canvas, while the statement
canvas-name <<- some-event; // double back-arrow
sends the signal to the parent canvas. For instance, when type of member of a frame, is also frame, then the canvas of the frame is the parent canvas for the canvas of its member. The signals sent to a canvas can be anything from erase/draw to mouse-click, etc.
9.7. Operator <<
For a menu item, RadioButton and Checkbox the expression on right-hand side must evaluate to an object of boolean type. Sending True will make the entity on the left checked, and sending Flase with make it unchecked: "gui-entity << expression;".
When gui-entity is Field, Label or Button, expression must evaluate to a string. For a ComboBox the expression must evaluate to an integer (discrete numeric) object.
For Field, the string is appended to its contents.
For ComboBox the integer is used as index (0-based) for setting its current selection.
For Label and Button entities, the string becomes their labels (names like STOP for a button, or text of the Label).
9.7. Operator >>
This is the opposite of operator <<. For menu item, RadioButton and CheckBox it makes its operand True when the entity is checked and Flase otherwise.
For Field, the string returned is the contents of the Field.
For ComboBox returns an integer for the index of its current selection.
For Label and Button it returns the label (name) of entity.
9.9. Operator =
For a Field "gui-entity = expression;" the expression must evaluate to a string, which replaces the contents of Field.
For RadioButton and CheckBox, the string becomes their lables (names).
For ComboBox, the string is appended to its list, if not already there, and becomes its selection.
9.10. Operator +
This operator appends a string to a List/ComboBox: "gui-entity + expression;".
9.11. Operator -
The gui-entity must be a ComboBox/List, and expression must evaluate to a string object. Then, "gui-entity - expression;" will remove the string from gui-entity. If the string is not in gui-entity, nothing happens.
9.12. Operator []
For an integer n, "gui-entity[n]" becomes a reference to the n-th element in a List/ComboBox. Note that the first element is at index 0. The reference can be used to change the string value of the element, just like array cells: gui-entity[n] = "New Value";
9.13. Operator ?
When gui-entity is a List/Combobox, and expression results in a string, "gui-entity ? expression;" returns the index of string in gui-entity. The index count starts with 0 for the first element, etc.
9.14. Size of list
The expression "size(gui-entity)" evaluates to the number of elements in gui-entity for a List or ComboBox.