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
Debug Section
Atomic Section
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. Fundamental types are built-in (predefined).
Actual names are as listed above, all in lower-case.
Remark. void
is also a built-in type, shown as VOID
in rules. Objects of type void cannot be created.
Remark. Z++ compiler creates
two objects: True and False. These are boolean and can
be used directly
or to initialize other boolean objects.
Remark. mutex is a built-in
type, so you simply declare: "mutex mtx;".
The creation and initialization of mtx object
is done by Z47 processor.
Operations lock/release are done with prefix operators +/-. Thus,
"+mtx;" locks the mutex object mtx
and "-mtx;" releases it.
Remark. int/uint are 32-bits
long, while long/ulong are 64 bits long.
<-------------------------------+
| |
+--+--> 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.
Following three things are illustrated in the examples:
1. Compiler can de-reference a pointer to an array.
2. In an array declaration, the identifier can be placed last.
3. Array literal initializer using {}.
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. // However, for a pointer to an array, compiler can do the de-referencing, as
// shown by the second statement.
output << (*q)[0][1] << '\n'; 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));
// For an array declaration you can put the identifier last. This is particularly
// useful for parameter declarations. The following two statement are equivalent.
int* a[3]*;
int *[3]* a;
// Both make 'a' a pointer to an array of pointers to int.
// A static array, i.e. sizes of dimensions known at compile time, can be initialized
// using literal initializer, {}.
int a[3] = {7, 47, 97};
// For a two dimensional array use nested {}.
int a[2][2] = { {4, 5} , {6, 7} };
// The initializers can be any expression that returns an object of suitable type,
// including other objects, or constructors.
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", or "library-name.so".
foreign-specification --->>> +--> "C++" ----+--->
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!
debug_section --->>> debug--> statement-block --> enddebug-->
Remarks. The control statement debug-section is only compiled in a debug build, and has no effect for a release build.
It can include any of the control statements, except itself. That is, it cannot be nested.
It can be include in any statement-block, such as loop bodies, legs of if-else statement, etc.
atomic_section --->>> atomic--> statement-block --> endatomic-->
Remarks. When a thread enters atomic (critical) section, all other threads are suspended, until the thread executes "endatomic;".
It can include any of the control statements, except itself. That is, it cannot be nested.
It can be include in any statement-block, such as loop bodies, legs of if-else statement, etc.
An atomic section cannot include calls to functions/methods.
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 ignore a null pointer. No action is taken.
Destroy will also 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;
// typedef is not needed, but makes things easier.
typedef ppValue3 value**[3];
// 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 ppValue3, which is a static array of two-level
// pointers to objects of type value.
ppValue3** vip[first][second];
// Initialize vip
for (int one = 0; one < first; one++)
for (int two = 0; two < second; two++)
vip[one][two] = new ppValue3*(new ppValue3);
for (int index = 0; index < 3; index++)
vip[one][two][index] = new value*(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.