10.2 Activating the C++ translator10.2 Activating the C++ translator
10 C++ translator 10 C++ translator
10.4 Variable declarations 10.4 Variable declarations
10.3 Declarations

10.3 Declarations

A declaration translates to one or more type, constant, function or variable declarations as described for the various kinds of declarations.

If the declaration occurs in a class expression, the declarations are placed at the outermost level.

Declarations from local expressions are included in-line when they are only variables and constant values. Otherwise they are placed in a separate namespace outside the definition where the local expression occurred: see section 10.8.23.

Type declarations are always placed in the header file. To accommodate C++'s requirement of declaration before use the produced declarations are sorted according to kind in the following order:
type definitions (including those from embedded objects)
embedded objects (in namespaces)
constants and functions
variables
test cases

10.3.1 Scheme declarations

Apart from the top level module, schemes are only translated when they are instantiated as objects. So a scheme that is instantiated several times will therefore be translated several times. This may appear wasteful, but it only affects the size of the C++ source, not the final object code, and saves the need for the restrictions on scheme parameters that would be needed if templates were used for schemes.

10.3.2 Object declarations

An object translates as its translated declarations placed within a namespace of the same name as the object.

An object definition in RSLC++ cannot have a formal array parameter.

10.3.3 Type declarations

A type declaration translates to one or more type definitions for each type definition in its type definition list. Several type definitions generate C++ class definitions with member functions for test of equality and so on and accompanied by the specified constructor, destructor and reconstructor functions. These type definitions include short record definitions, variant definitions and abbreviation definitions that name product types. Recursive data structures may be specified by means of record variants, which translate to dynamic structures. All classes are declared public as structures, which are prototype declared in front of the definitions proper. All produced type definitions are placed in the header part.

Sort definitions

A sort definition translates to an almost empty C++ struct in order to support hand-translation.

                                     
 type Sort

gives a warning message and translates to (input/output operators omitted):

struct Sort/*INCOMPLETE: abstract type*/{
bool operator==(const Sort& RSL_v) const{
return true;
}

bool operator!=(const Sort& RSL_v) const{
return false;
}

};

Variant definitions

A variant definition translates to a struct containing a tag field identifying the variant-choice and a pointer to the record variant. Allocation and deallocation of record variant structures are handled by the various constructor and member functions by means of reference counts. The following struct is the base class of all record variants

struct RSL_class {
  int refcnt;
  RSL_class () {refcnt = 1;}
};

The variant

                                     
 type
 V ==  
 Vconst |
 Vint(Int) |
 Vrec(d1Vrec : Int ↔ r1Vrec, d2Vrec : V ↔ r2Vrec)

translates to (input/output operators included)

// From the .h file ...
// type and constant declarations and inline functions
static const int RSL_Vconst_tag = 1;
static const int RSL_Vint_tag = 2;
static const int RSL_Vrec_tag = 3;
struct RSL_Vint_type;
struct RSL_Vrec_type;
struct V{
int RSL_tag;
RSL_class* RSL_ptr;
 V(const int RSL_p0 = 0){
RSL_tag = RSL_p0;
RSL_ptr = 0;
}

 V(const RSL_Vint_type* RSL_v){
RSL_tag = RSL_Vint_tag;
RSL_ptr = (RSL_class*)RSL_v;
}

 V(const RSL_Vrec_type* RSL_v){
RSL_tag = RSL_Vrec_tag;
RSL_ptr = (RSL_class*)RSL_v;
}

void RSL_destructor();
 ~V(){
RSL_destructor();
}

 V(const V& RSL_v);
const V& operator=(const V& RSL_v);
bool operator==(const V& RSL_v)  const;
bool operator!=(const V& RSL_v) const{
return !operator==(RSL_v);
}

};
extern string RSL_to_string(const V& RSL_v);
#ifdef RSL_io
extern ostream& operator<<(ostream& RSL_os, const V& RSL_v);

extern istream& operator>>(istream& RSL_is, V& RSL_v);

#endif //RSL_io
static const V Vconst(RSL_Vconst_tag);
struct RSL_Vint_type : RSL_class, RSLProduct1<int, RSL_constructor_fun>{
 RSL_Vint_type(){}

 RSL_Vint_type(const int RSL_p1) :
  RSL_class(), RSLProduct1<int, RSL_constructor_fun>::RSLProduct1(RSL_p1){}

};
extern V Vint(const int RSL_p1);
struct RSL_Vrec_type : RSL_class, RSLProduct2<int, V, RSL_constructor_fun>{
 RSL_Vrec_type(){}

 RSL_Vrec_type(const int RSL_p1, const V& RSL_p2) :
  RSL_class(),
  RSLProduct2<int, V, RSL_constructor_fun>::RSLProduct2(RSL_p1, RSL_p2){}

};
extern V Vrec(const int RSL_p1, const V& RSL_p2);
inline int d1Vrec(const V& RSL_v){
#ifdef RSL_pre

if (RSL_v.RSL_tag != RSL_Vrec_tag)
{
RSL_fail("V.rsl:6:10: Destructor d1Vrec applied to wrong variant");
}
#endif //RSL_pre

return ((RSL_Vrec_type*)RSL_v.RSL_ptr)->RSL_f1;
}

inline V d2Vrec(const V& RSL_v){
#ifdef RSL_pre

if (RSL_v.RSL_tag != RSL_Vrec_tag)
{
RSL_fail("V.rsl:6:35: Destructor d2Vrec applied to wrong variant");
}
#endif //RSL_pre

return ((RSL_Vrec_type*)RSL_v.RSL_ptr)->RSL_f2;
}

extern V r1Vrec(const int RSL_p0, const V& RSL_v);
extern V r2Vrec(const V& RSL_p0, const V& RSL_v);

The templates RSLProductn (1≤n≤10) are defined in RSL_Prod.h. Each takes as arguments the n types making the product plus a function generating a string for a constructor. This function is RSL_constructor_fun (which generates the empty string) for products and variant components, and a function generating "mk_T" for a record type T.

// From the .cc file ...
// RSL constructor, destructor and reconstructor functions
void V::RSL_destructor(){
switch (RSL_tag) {
case RSL_Vint_tag:
if (--(RSL_ptr->refcnt) == 0)
{
delete (RSL_Vint_type*)RSL_ptr;
}
break;
case RSL_Vrec_tag:
if (--(RSL_ptr->refcnt) == 0)
{
delete (RSL_Vrec_type*)RSL_ptr;
}
break;
}
}


 V::V(const V& RSL_v){
switch (RSL_v.RSL_tag) {
case RSL_Vint_tag:
case RSL_Vrec_tag:
RSL_v.RSL_ptr->refcnt++;
}
RSL_tag = RSL_v.RSL_tag;
RSL_ptr = RSL_v.RSL_ptr;
}


const V& V::operator=(const V& RSL_v){
if (this == &RSL_v)
{
return RSL_v;
}
switch (RSL_v.RSL_tag) {
case RSL_Vint_tag:
case RSL_Vrec_tag:
RSL_v.RSL_ptr->refcnt++;
}
RSL_destructor();
RSL_tag = RSL_v.RSL_tag;
RSL_ptr = RSL_v.RSL_ptr;
return *this;
}


bool V::operator==(const V& RSL_v) const{
if (RSL_tag != RSL_v.RSL_tag)
{
return false;
}
switch (RSL_tag) {
case RSL_Vint_tag:
return *(RSL_Vint_type*)RSL_ptr == *(RSL_Vint_type*)RSL_v.RSL_ptr;
case RSL_Vrec_tag:
return *(RSL_Vrec_type*)RSL_ptr == *(RSL_Vrec_type*)RSL_v.RSL_ptr;
default:
return true;
}
}


string RSL_to_string(const V& RSL_v){
string RSL_Temp_0;

switch (RSL_v.RSL_tag) {
case RSL_Vconst_tag:
RSL_Temp_0 = "Vconst";
break;
case RSL_Vint_tag:
RSL_Temp_0 = "Vint" + RSL_to_string(*(RSL_Vint_type*)RSL_v.RSL_ptr);
break;
case RSL_Vrec_tag:
RSL_Temp_0 = "Vrec" + RSL_to_string(*(RSL_Vrec_type*)RSL_v.RSL_ptr);
break;
default:
RSL_Temp_0 = "Unknown variant value";
break;
}
return RSL_Temp_0;
}

#ifdef RSL_io
ostream& operator<<(ostream& RSL_os, const V& RSL_v){
switch (RSL_v.RSL_tag) {
case RSL_Vconst_tag:
RSL_os << "Vconst";
break;
case RSL_Vint_tag:
RSL_os << "Vint" << *(RSL_Vint_type*)RSL_v.RSL_ptr;
break;
case RSL_Vrec_tag:
RSL_os << "Vrec" << *(RSL_Vrec_type*)RSL_v.RSL_ptr;
break;
default:
RSL_os << "Unknown variant value";
break;
}
return RSL_os;
}


const RSL_input_token_type RSL_Vconst_token = Token_StartIndex + 1;

const RSL_input_token_type RSL_Vint_token = Token_StartIndex + 2;

const RSL_input_token_type RSL_Vrec_token = Token_StartIndex + 3;

static void RSL_input_token_V(istream& RSL_is, RSL_input_token_type& RSL_token){
char RSL_buf[128];

RSL_fetch_token(RSL_is, RSL_token, RSL_buf);
if (RSL_token == RSL_constructor_token)
{
if (RSL_streq(RSL_buf, "Vconst"))
{
RSL_token = RSL_Vconst_token;
return;
}
if (RSL_streq(RSL_buf, "Vint"))
{
RSL_token = RSL_Vint_token;
return;
}
if (RSL_streq(RSL_buf, "Vrec"))
{
RSL_token = RSL_Vrec_token;
return;
}
RSL_token = RSL_error_token;
}
}


istream& operator>>(istream& RSL_is, V& RSL_v){
RSL_input_token_type RSL_token;

V RSL_temp;

RSL_class* RSL_ptr = 0;

RSL_input_token_V(RSL_is, RSL_token);
switch (RSL_token) {
case RSL_Vconst_token:
RSL_temp = V(RSL_Vconst_tag);
break;
case RSL_Vint_token:
RSL_ptr = new RSL_Vint_type;
RSL_is >> *(RSL_Vint_type*)RSL_ptr;
if (RSL_is)
{
RSL_temp = V((RSL_Vint_type*)RSL_ptr);
}
break;
case RSL_Vrec_token:
RSL_ptr = new RSL_Vrec_type;
RSL_is >> *(RSL_Vrec_type*)RSL_ptr;
if (RSL_is)
{
RSL_temp = V((RSL_Vrec_type*)RSL_ptr);
}
break;
default:
RSL_is.clear(ios::badbit);
break;
}
if (RSL_is)
{
RSL_v = RSL_temp;
}
else
{
RSL_is.clear(ios::badbit);
}
return RSL_is;
}


#endif //RSL_io
V Vint(const int RSL_p1){
RSL_Vint_type* RSL_v = new RSL_Vint_type(RSL_p1);

if (!RSL_v)
{
abort();
}
return V(RSL_v);
}

V Vrec(const int RSL_p1, const V& RSL_p2){
RSL_Vrec_type* RSL_v = new RSL_Vrec_type(RSL_p1, RSL_p2);

if (!RSL_v)
{
abort();
}
return V(RSL_v);
}

V r1Vrec(const int RSL_p0, const V& RSL_v){
#ifdef RSL_pre

if (RSL_v.RSL_tag != RSL_Vrec_tag)
{
RSL_fail("V.rsl:6:27: Reconstructor r1Vrec applied to wrong variant");
}
#endif //RSL_pre

return Vrec(RSL_p0, ((RSL_Vrec_type*)RSL_v.RSL_ptr)->RSL_f2);
}

V r2Vrec(const V& RSL_p0, const V& RSL_v){
#ifdef RSL_pre

if (RSL_v.RSL_tag != RSL_Vrec_tag)
{
RSL_fail("V.rsl:6:50: Reconstructor r2Vrec applied to wrong variant");
}
#endif //RSL_pre

return Vrec(((RSL_Vrec_type*)RSL_v.RSL_ptr)->RSL_f1, RSL_p0);
}

Constructors, destructors and reconstructors translate as the identifier or operator does. Wildcard constructors are not accepted.

When the translated code is compiled with the RSL_io flag, a handwritten C++ compilation unit can perform input/output of variant values. For example:

void main(){
V aV, anotherV;
aV = Vint(42);
cout << "First value: " << aV << "\n";
cout << "Give a value of type V:\n";
cin >> anotherV;
cout << "Second value: " << anotherV << "\n";
}
The following is an example of an execution of this program (user lines are marked with #):
First value: Vint(42)
Give a value of type V:
Vrec(1957, Vint(1969)) #
Second value: Vrec(1957,Vint(1969))

Union definitions

Not accepted.

Short record definitions

A short record definition translates to a C++ class definition including member functions and function definitions that implement the constructor, destructor and reconstructor functions. Note that a short record translates differently from a variant definition -- the short record translates without the use of pointers.
                                     
 type
 Complex :: re: Real ↔ r im: Real ↔ i

translates to

// from the .h file ...
char* RSL_mk_Complex_fun(){
return "mk_Complex";
}

typedef RSLProduct2<double, double, RSL_mk_Complex_fun> Complex;
inline Complex mk_Complex(const double RSL_p1, const double RSL_p2){
return Complex(RSL_p1, RSL_p2);
}

inline double re(const Complex& RSL_v){
return RSL_v.RSL_f1;
}

inline double im(const Complex& RSL_v){
return RSL_v.RSL_f2;
}

extern Complex r(const double RSL_p0, const Complex& RSL_v);
extern Complex i(const double RSL_p0, const Complex& RSL_v);
// from the .cc file ...
Complex r(const double RSL_p0, const Complex& RSL_v){
return Complex(RSL_p0, RSL_v.RSL_f2);
}

Complex i(const double RSL_p0, const Complex& RSL_v){
return Complex(RSL_v.RSL_f1, RSL_p0);
}

Abbreviation definitions

An abbreviation definition translates to a type definition of a universal type whose name is derived from the structure of the type (see section 10.12 on universal types), plus a definition of the identifier as the universal type. For each translation there is at most one definition of each universal type.
                                     
 type
 B = C × C,
 C = {| n : Int • n ∈ {0..7} |},
 D = Nat × Int,
 E = D → D,
 F = C × D → D

translates to

typedef int C;

typedef RSLProduct2<int, int, RSL_constructor_fun> RSL_IxI;

typedef RSL_IxI D;

typedef RSL_IxI (* RSL_IxIfIxI)(const RSL_IxI );

typedef RSL_IxIfIxI E;

typedef RSL_IxI (* RSL_Ix6IxI9fIxI)(const int , const RSL_IxI );

typedef RSL_Ix6IxI9fIxI F;

typedef RSL_IxI B;

10.3.4 Value declarations

Typings

Typings are accepted with a warning that they are incomplete

Explicit value definitions

An explicit value definition translates to a constant declared in the header file and defined in the body file. For example
                                     
 value
 low: Int = 0,
 high: Int = low + 100,
 max : Int = Max(low, high),
 p : Int × Bool = (7, true)

translates to

// Header file:
extern const int low;
extern const int high;
extern const int max;
extern const RSL_IxB p;
// Body file:
const int low = 0;
const int high = low + 100;
const int max = Max(low, high);
const RSL_IxB p = RSL_IxB(7, true);

Additional code, included if RSL_pre is defined, is generated if any constant types are subtypes, to check that the values of the constants are in the subtypes.

An explicit value definition which defines a function translates by means of a C++ pointer to function type. E.g.

                                     
 value
 f : Int × IntInt = Max

translates to

// Header file:
typedef int (* RSL_IxIfI)(const int , const int );
extern const RSL_IxIfI f;
// Body file:
const RSL_IxIfI f = Max;

Implicit value definitions

Implicit value definitions are accepted with a warning that they are incomplete.

Explicit function definitions

An explicit function definition translates to a C++ function definition.

As an example

                                     
 value
 sqr : RealReal
 sqr(x) ≡ x*x,
 
 + : V × Int → V
 v + i ≡ Vrec(i, v)

(in the context of the type V defined earlier) translates to

double sqr(const double x_38B){
return x_38B * x_38B;
}

V RSL_PLUS_op(const V& v_4B3, const int i_4B7){
return Vrec(i_4B7, v_4B3);
}

Note that a user-defined operator translates into a function with a name derived from the operator name rather than into an operator. This eases the translation of such operators. In particular it makes them all translatable. The names are given below.

Operator Function name
= RSL_EQ_op
RSL_NOTEQ_op
== RSL_EQEQ_op
> RSL_GT_op
< RSL_LT_op
RSL_GEQ_op
RSL_LEQ_op
RSL_PSUP_op
RSL_PSUB_op
RSL_SUP_op
RSL_SUB_op
RSL_ISIN_op
RSL_NOTISIN_op
\ RSL_MOD_op
^ RSL_CONC_op
RSL_UNION_op
RSL_OVER_op
* RSL_AST_op
/ RSL_DIV_op
# RSL_HASH_op
RSL_INTER_op
RSL_EXP_op
abs RSL_ABS_op
int RSL_INT_op
real RSL_REAL_op
card RSL_CARD_op
len RSL_LEN_op
inds RSL_INDS_op
elems RSL_ELEMS_op
hd RSL_HD_op
tl RSL_TL_op
dom RSL_DOM_op
rng RSL_RNG_op
+ RSL_PLUS_op
- RSL_MINUS_op
Function names for user-defined operators
 

Additional code, included if RSL_pre is defined, is generated if any parameter types are subtypes or if the function has a precondition.

Access descriptors are ignored. The kind of function arrow (→ or -~->) does not matter.

Only one formal function parameter is accepted. It is not possible to translate

                                     
 f : IntIntInt
 f(x)(y) ≡ ...

It is not required that the number of parameters matches the number of components in the domain of the function's type expression. For example, the following are all accepted:

                                     
 type
 U = Int × Bool
 value
 f1: Int × BoolBool
 f1(x, y) ≡ ...,
 f2: (Int × Bool) → Bool
 f2(x, y) ≡ ...,
 f3: U → Bool
 f3(x, y) ≡ ...,
 f4: U × IntBool
 f4(x, y) ≡ ...,
 f5: (Int × Bool) × IntBool
 f5(x, y) ≡ ...
 f6: (Int × Bool) × IntBool
 f6(x) ≡ ...
 f7: Int × BoolBool
 f7(x) ≡ ...,
 f8: (Int × Bool) → Bool
 f8(x) ≡ ...,
 f9: U → Bool
 f9(x) ≡ ...,
 f10: U × IntBool
 f10((x, y), z) ≡ ...,
 f11: (Int × Bool) × BoolBool
 f11((x, y), z) ≡ ...

Implicit function definitions

Implicit function definitions are accepted with a warning that they are incomplete.
Chris George, April 17, 2008

10.3 Declarations
10.2 Activating the C++ translator10.2 Activating the C++ translator
10 C++ translator 10 C++ translator
10.4 Variable declarations 10.4 Variable declarations