Features

Quick tour of

Cforall

features. What makes

Cforall

better!


Declarations

Alternative Declaration Syntax

Left-to-right declaration syntax, except bit fields. All declaration symbols have identical meaning, only the order (left-to-right) changes.

C∀C
* int p;        // pointer to int
[5] int a;      // array of 5 ints
* [5] int pa;   // pointer to array of 5 ints
[5] * int ap;   // array of 5 pointers to int
* int p1, p2;   // distribute pointer
const * const int cpc;
const * [5] const int cpac;
extern [5] int xa;
static * const int sp;
* [ int ] ( int ) fp; // pointer to function
                      // returning int and taking int
* [ * [ ] int ] ( int ) gp;
[5] * [ * [ int ] (int) ] ( int ) hp;
(* int)x;
sizeof( [5] * int );
int * p;
int a[5];
int (* pa)[5];
int * ap[5];
int * p1, * p2;
const int * const cpc;
const int * const cpac[5]
extern int xa[5]
static const int * sp;
int (* fp)( int )

int (* (* gp)( int ))[ ];
int (* (* hp[5])( int ))( int );
(int *)x
sizeof( * int [5] );

Rebindable References

Multi-level rebindable references, as an alternative to pointers, to reduces syntactic noise.

int x = 1, y = 2, z = 3;
int * p1 = &x, ** p2 = &p1,  *** p3 = &p2, // pointers to x
	& r1 = x,  && r2 = r1,   &&& r3 = r2;  // references to x
int * p4 = &z, & r4 = z;

*p1 = 3; **p2 = 3; ***p3 = 3;			// change x
 r1 =  3;	r2 = 3;		r3 = 3;			// change x: implicit dereference *r1, **r2, ***r3
**p3 = &y;	*p3 = &p4;					// change p1, p2
// cancel implicit dereferences (&*)**r3, (&(&*)*)*r3, &(&*)r4
&r3 = &y; &&r3 = &&r4;					// change r1, r2

A reference is a handle to an object, like a pointer, but is automatically dereferenced the specified number of levels. Referencing (address-of &) a reference variable cancels one of the implicit dereferences, until there are no more implicit references, after which normal expression behaviour applies.

A C++ non-rebindable reference with null pointer checking is provided by a C∀ constant reference.

int i;
int & const cr = i;						// must initialized, no null pointer
cr = 3;									// change i through reference cr
&cr = &i;								// fails, assignment to read-only reference cr

const int ci = 3;
const int & const ccr = ci;				// must initialized, no null pointer
ccr = 3;								// fails, assignment to read-only variable ci
&ccr = &i;								// fails, assignment to read-only reference ccr

int & const & const crcr = cr;			// must initialized, no null pointer
crcr = 3;								// change i through double reference crcr
&crcr = &i;								// fails, assignment to read-only 1st reference crcr
&&crcr = &cr;							// fails, assignment of read-only 2nd reference crcr

const int & const & const ccrcr = ccr;		// must initialized, no null pointer
ccrcr = 3;								// fails, assignment of read-only variable ci
&ccrcr = &ci;							// fails, assignment of read-only 1st reference ccrcr
&&ccrcr = &ccr;							// fails, assignment of read-only 2nd reference ccrcr

Tuple

Formalized lists of elements, denoted by [ ], with parallel semantics.

int i;
double x, y;
int f( int, int, int );
f( 2, x, 3 + i );						// technically ambiguous: argument list or comma expression?
f( [ 2, x, 3 + i ] );					// formalized (tuple) element list
[ i, x, y ] = 3.5;						// i = 3.5, x = 3.5, y = 3.5
[ x, y ] = [ y, x ];					// swap values
[ int, double, double ] t;				// tuple variable
* [ int, double, double ] pt = &t;		// tuple pointer
t = [ i + 5, x / 2.0, y * 3.1 ];		// combine expressions into tuple
[ i, x, y ] = *pt;						// expand tuple elements to variables
[i, (x, y)] = 3;						// comma expression in tuple
x = t.1;								// extract 2nd tuple element (zero origin)
[ y, i ] = [ t.2, t.0 ];				// reorder and drop tuple elements
pt->2 = 5.1;							// change 3rd tuple element

Tuple-Returning Function

Functions may return multiple values using tuples.

[ int, int ] div( int i, int j ) {		// compute quotient, remainder
	return [ i / j, i % j ];			// return 2 values
}
int g( int, int );						// 2 parameters
int main() {
	int quo, rem;
	[ quo, rem ] = div( 3, 2 );			// expand tuple elements to variables
	g( div( 5, 3 ) );					// expand tuple elements to parameters
	quo = div( 7, 2 ).0;				// extract quotient element
}

Tuple Examples

Tuple Member Access

Create a tuple by selecting multiple fields by field name/index to rearrange, drop, or duplicate fields. The aggregate expression to the left of the . in a member tuple expression is evaluated exactly once.

[ double, float ] f( int, double ) {}
struct S { char x; int y; double z; } s;
s.[ x, y, z ] = 1;						// select by name: s.x = 1, s.y = 1, s.z = 1
s.[ y, z, x ] = [ 3, 3.2, 'x' ];		// rearrange: s.y = 3, s.z = 3.2, s.x = 'x'
f( s.[ y, z ] );						// drop: f( s.y, s.z )
f( s.[ y, y ] );						// duplicate: f( s.y, s.y )

s.[ 0, 1, 3 ] = 1;						// select by name: s.0 = 1, s.1 = 1, s.2 = 1
s.[ 1, 3, 0 ] = [ 3, 3.2, 'x' ];		// rearrange: s.1 = 3, s.3 = 3.2, s.0 = 'x'
f( s.[ 1, 3 ] );						// drop: f( s.y, s.z )
f( s.[ 1, 1 ] );						// duplicate: f( s.y, s.y )

[ int, int, long, double ] x;
void f( double, long ) {}
f( x.[ 0, 3 ] );						// select by index: f( x.0, x.3 )
x.[ 0, 1 ] = x.[ 1, 0 ];				// [ x.0, x.1 ] = [ x.1, x.0 ]
[ long, int, long ] y = x.[ 2, 0, 2 ];

struct A { double i; int j; };
struct B { int * k; short l; };
struct C { int x; A y; B z; } v;
v.[ x, y.[ i, j ], z.k ];
[ int, float, double ] f() {};
[ double, float ] x = f().[ 2, 1 ];		// f() called once

Constructor / Destructor

Implicit initialization and de-initialization (like C++). A constructor/destructor name is denoted by '?{}' / '^?{}', where '?' denotes the operand and '{' '}' denote the initialization parameters.

struct VLA { int size, * data; };		// variable length array of integers
void ?{}( VLA & vla ) with ( vla ) {	// default constructor
    size = 0;  data = 0p;
}
void ?{}( VLA & vla, int size, char fill = '\0' ) { // initialization
	vla.[ size, data ] = [ size, alloc( size, fill ) ];
}
void ?{}( VLA & vla, VLA val ) with( val ) { // copy, deep
	vla.[ size, data ] = [ size, alloc( size, data ) ];
}
void ^?{}( VLA & vla ) with ( vla ) {	// destructor
    free( data );
    size = 0;  data = 0p;
}
int main() {
	VLA  x,		y = { 20, 0x01 },	z = y;	// z copies y
	//   x{};	y{ 20, 0x01 };		z{ z, y };
	x{ 5 };								// allocate x
	^x{};								// deallocate x
	^z{};								// deallocate z
	z{ 5, '\xff' };						// reallocate z
	^y{};								// deallocate y
	y{ z };								// reallocate y, copies z
	x{ 10, '\0' };						// reallocate x
}	//  ^z{};  ^y{};  ^x{};

The alternative to constructor initialization is C designations, which are similar to named parameters in Python and Scala, except specific to aggregate initializers. Note C∀ designations use a colon separator, rather than C assignment, because the latter syntax conflicts with new language features.

struct A {
	int w, x, y, z;
};
A a0 = { .x : 4, .z : 1, .x : 8 };				// A a0 = { 0, 8, 0, 1 };
A a1 = { 1, .y : 7, 6 };						// A a1 = { 1, 0, 7, 6 };
A a2[4] = { [2] : a0, [0] : a1, { .z : 3 } };	// A a2[4] = { a1, { 0, 0, 0, 3 }, a0, { 0, 0, 0, 0 } };

Any field not explicitly initialized is initialized as if it had static storage duration, implying 0 fill. Later initializers override earlier initializers, so a field with more than one initializer is only initialized by its last initializer. For example, in the initialization of a0, x is designated twice, and thus initialized to 8. A designator specifies the current field for initialization, and any undesignated fields pick up where the last initialization left off. For example, in the initialization of a1, the initializer of y is 7, and the unnamed initializer 6 initializes the next field, z. A designator name can be a subscript. For example, in the initialization of a2, the initializer [2] means the second element, and then the previous rules apply for the remaining initializers.

Enumeration

An enumeration is a compile-time mechanism to alias names to constants, like typedef is a mechanism to alias names to types. An enumeration type (shortened to enum) is a set of names, each called an enumerator, aliased to a compile-time constant value.

enum Days { Mon, Tue, Wed, Thu, Fri, Sat, Sun }; // enumeration definition, set of 7 enumerators & constants
Days days = Mon; // enumeration declaration and initialization

The set of enumerators is injected into the variable namespace at the definition scope. Hence, enumerators may be overloaded with variable, enum, and function names.

int Foo;								// type/variable separate namespaces
enum Foo { Bar };
enum Goo { Bar };						// overload Foo.Bar
double Bar;								// overload Foo.Bar, Goo.Bar

An anonymous enumeration injects enumerators with specific values into a scope.

enum { Prime = 103, BufferSize = 1024 };

which is better than using:

C preprocessorC constant declarations
#define Mon 0
...
#define Sun 6
const int Mon = 0,
          ...,
          Sun = 6;

because the enumeration is succinct, has automatic numbering, can appear in case labels, does not use storage, and is part of the language type-system.

Enum Type

The enumeration can be any type, and an enumerator's value comes from this type. Because an enumerator is a constant, it cannot appear in a mutable context, e.g. Mon = Sun is disallowed, and has no address (it is an rvalue). C∀ provides an automatic conversion from an enumerator to its constant's base-type.

The default enum type is int. If no values are specified for an integral enum type, the enums are automatically numbered starting at zero by one from left to right. If a value is specified, numbering continues by one from that value. If an enumerator value is a constant expression, the compiler performs constant-folding to obtain the constant value.

C∀ allows other integral types with associated values.

enum() Mode { O_RDONLY, O_WRONLY, O_CREAT, O_TRUNC, O_APPEND };
enum( char ) Letter { A = 'A',  B,  C,  I = 'I',  J,  K };
enum( long long int ) BigNum { X = 123_456_789_012_345,  Y = 345_012_789_456_123 };

The empty type, (), implies the enums are pure symbols without values; hence, there is no default conversion to int.

Non-integral enum types must be explicitly initialized, e.g., double is not automatically numbered by one.

// non-integral numeric
enum( double ) Math { PI_2 = 1.570796, PI = 3.141597, E = 2.718282 }
// pointer
enum( char * ) Name { Fred = "Fred",  Mary = "Mary", Jane = "Jane" };
int i, j, k;
enum( int * ) ptr { I = &i,  J = &j,  K = &k };
enum( int & ) ref { I = i,   J = j,   K = k };
// tuple
enum( [int, int] ) { T = [ 1, 2 ] };
// function
void f() {...}   void g() {...}
enum( void (*)() ) funs { F = f,  F = g };
// aggregate
struct S { int i, j; };
enum( S ) s { A = { 3,  4 }, B = { 7,  8 } };
// enumeration
enum( Letter ) Greek { Alph = A, Beta = B, /* more enums */  }; // alphabet intersection

Enumeration Greek may have more or less enumerators than Letter, but the enumerator values must be from Letter. Therefore, Greek enumerators are a subset of type Letter and are type compatible with enumeration Letter, but Letter enumerators are not type compatible with enumeration Greek.

Math m = PI;							// allowed
double d = PI;							// allowed, conversion to base type
m = E;									// allowed
m = Alph;								// disallowed
m = 3.141597;							// disallowed
d = m;									// allowed
d = Alph;								// disallowed
Letter l = A;							// allowed
Greek g = Alph;							// allowed
l = Alph;								// allowed, conversion to base type
g = A;									// disallowed

A constructor cannot be used to initialize enumerators because a constructor executes at runtime. A fallback is explicit C-style initialization using @=.

enum( struct vec3 ) Axis { Up @= { 1, 0, 0 }, Left @= { 0, 1, 0 }, Front @= { 0, 0, 1 } }

Enumeration Inheritance

C∀ Plan-9 inheritance may be used with enumerations.

enum( char * ) Name2 { inline Name, Jack = "Jack", Jill = "Jill" };
enum /* inferred */ Name3 { inline Name2, Sue = "Sue", Tom = "Tom" };

Enumeration Name2 inherits all the enumerators and their values from enumeration Name by containment, and a Name enumeration is a subtype of enumeration Name2. Note, enumerators must be unique in inheritance but enumerator values may be repeated.

The enum type for the inheriting type must be the same as the inherited type; hence the enum type may be omitted for the inheriting enumeration and it is inferred from the inherited enumeration, as for Name3. When inheriting from integral types, automatic numbering may be used, so the inheritance placement left to right is important.

Specifically, the inheritance relationship for Names is:

Name ⊂ Name2 ⊂ Name3 ⊂ const char *		// enum type of Name

For the given function prototypes, the following calls are valid.

void f( Name );
void g( Name2 );
void h( Name3 );
void j( const char * );
f( Fred );
g( Fred );   g( Jill );
h( Fred );   h( Jill );   h( Sue );
j( Fred );   j( Jill );   j( Sue );   j( "Will" );

Note, the validity of calls is the same for call-by-reference as for call-by-value, and const restrictions are the same as for other types.

Nested Functions

Nested functions and call-site inferencing provide a localized form of inheritance.

forall( T | { int ?<?( T, T ); } ) void qsort( const T * arr, size_t size ) { /* use C qsort */ }
int main() {
	int ?<?( double x, double y ) { return x > y; } // locally override behaviour
	qsort( vals, 10 );					// descending sort
}

The local version of ?<? performs ?>? overriding the built-in ?<? so it is passed to qsort.

Postfix Function Call

Alternative call syntax where the argument appears before the function name, where ?` denotes a postfix-function name and ` denotes a postfix-function call. Common usage is for converting basic literals into user literals.

postfix functionconstant argument callvariable argument callpostfix function pointer
int ?`h( int s );
int ?`h( double s );
int ?`m( char c );
int ?`m( const char * s );
int ?`t( int a, int b, int c );
0`h;
3.5`h;
'1'`m;
"123" "456"`m;
[1,2,3]`t;
int i = 7;
i`h;
(i + 3)`h;
(i + 3.5)`h;

int (* ?`p)( int i );
?`p = ?`h;
3`p;
i`p;
(i + 3)`p;

To pass multiple arguments to a postfix function requires a tuple, e.g., [1, 2, 3]`t, which forms a single argument that is flattened into the multiple arguments. Similarly, if the argument is an expression, it must be parenthesized, e.g., (i + 3)`h, or only the last operand of the expression is the argument, e.g., i + (3`h).

Postfix Call Examples

Qualifier Distribution

To reduce duplication, forall and storage-class qualifiers may be distributed over a group of functions/types. (See Polymorphic Type)

forall( T ) {							// distribution block, add forall qualifier to declarations
	struct stack {						// generic type
		T data[10];						// fixed size stack
		int depth;
	};
	inline {							// nested distribution block, add forall/inline to declarations
		void push( stack(T) & s, T value ) { s.data[s.depth++] = value; ... }
		// other generic stack operations
	}
}
stack(int) si;							// int stack
push( si, 3 );							// inline call
stack(double) sd;						// double stack
push( sd, 3.5 );						// inline call

Literals

Underscore Separator

Numeric literals allow underscores (like Ada/Java-8).

2_147_483_648;							// decimal constant
56_ul;									// decimal unsigned long constant
0_377;									// octal constant
0x_ff_ff;								// hexadecimal constant
0x_ef3d_aa5c;							// hexadecimal constant
3.141_592_654;							// floating point constant
10_e_+1_00;								// floating point constant
0x_ff_ff_p_3;							// hexadecimal floating point
0x_1.ffff_ffff_p_128_l;					// hexadecimal floating point long constant
L_"\x_ff_ee";							// wide character constant

Integral Suffixes

New integral suffixes hh (half of half of int) for char, h (half of int) for short, n (natural number) for int, and z for size_t integer types. New length suffixes for 8, 16, 32, 64, and 128 bit integer literals.

20hh			// signed char
21hhu			// unsigned char
22_h			// signed short int
23_uh			// unsigned short int
24n				// int
24un			// unsigned int
25z				// size_t

20_L8			// int8_t
21_ul8			// uint8_t
22l16			// int16_t
23ul16			// uint16_t
24_l32			// int32_t
25_ul32			// uint32_t
26_l64			// int64_t
27_l64u			// uint64_t
26L128			// int128
27_L128u		// unsigned int128






Pointer Suffix

New pointer suffix p for creating a pointer literal.

0p				// null pointer
1p				// alternative null pointer (type specific)
0x4ffffep		// hardware mapped register

0 / 1

Literals 0 and 1 are special in C: conditional ⇒ expr != 0 and ++/-- operators require 1.

struct S { int i, j; };
void ?{}( S & s, zero_t ) with( s ) { i = j = 0; }	// zero_t, no parameter name allowed
void ?{}( S & s, one_t ) with( s ) { [i, j] = 1; }	// one_t, no parameter name allowed
int ?!=?( S op, zero_t ) with( op ) { return i != 0 || j != 0; }
S & ?+=?( S & op, one_t ) with( op ) { i += 1; j += 1; return op; }

int main() {
	S s0 = 0, s1 = 1;					// call: ?{}( s0, zero_t ), ?{}( s1, one_t )
	if ( s1 ) {							// rewrite: s1 => s1 != 0 => ?!=?( s1, (S){ 0 } )
		s1 += 1;						// rewrite: ?+=?( s1, (S){ 1 } )
		s1 ++;							// predefined polymorphic pre/post ++ call ?+=?( S &, one_t )
		++ s1;
	}
}

Control Structures

C∀ identifies inconsistent, problematic, and missing control structures in C, and extends, modifies, and adds control structures to increase functionality and safety.

if / while Conditional

Extend if / while conditional with declarations, similar to for conditional. (Does not make sense for do-while.)

if ( int x = f() ) ...					// x != 0, x local to if/else statement (like C++)
if ( int x = f(), y = g() ) ...			// x != 0 && y != 0, x and y local to if/else statement
if ( int x = f(), y = g(); x < y ) ...	// x < y, x and y local to if/else statement
if ( struct S { int i; } x = { f() }; x.i < 4 ) ... // x.i < 4, S and x local to if/else statement

while ( int x = f() ) ...				// x != 0, x local to while statement (like C++)
while ( int x = f(), y = g() ) ...		// x != 0 && y != 0, x and y local to while statement
while ( int x = f(), y = g(); x < y ) ... // x and y local to while statement
while ( struct S { int i; } x = { f() }; x.i < 4 ) ... // x.i < 4, S and x local to while statement

case Clause

Extend case clause with list and subrange.

switch ( i ) {
  case 1, 3, 5: ...						// list
  case 6~9: ...							// subrange: 6, 7, 8, 9
  case 12~17, 21~26, 32~35: ...			// list of subranges
}

switch Statement

Extend switch statement declarations and remove anomalies.

switch ( x ) {
	int i = 0;							// allow declarations only at start, local to switch body
  case 0:
	...
	int j = 0;							// disallow, unsafe initialization
  case 1:
	{
			int k = 0;					// allow at lower nesting levels
			...
		case 2:							// disallow, case in nested statements (no Duff's device)
	}
  ...
}

choose Statement

Alternative switch statement with default break from a case clause.

choose ( i ) {
  case 1, 2, 3:
	...
	// implicit end of choose (switch break)
  case 5:
	...
	fallthrough;						// explicit fall through
  case 7:
	...
	break								// explicit end of choose (redundant)
  default:
	j = 3;
}

Non-terminating and Labelled fallthrough

Allow fallthrough to be non-terminating within a case clause or have a target label to common code from multiple case clauses.

non-terminatorlabelled
choose ( ... ) {
  case 3:
	if ( ... ) {
		... fallthru; // goto case 4
	} else {
		...
	}
	// implicit break
  case 4:




choose ( ... ) {
  case 3:
	... fallthrough common;
  case 4:
	... fallthrough common;

  common: // below fallthrough
		  // at case-clause level
	...	// common code cases 3/4
	// implicit break
  case 4:


choose ( ... ) {
  case 3:
	choose ( ... ) {
	  case 4:
		for ( ... ) {
			// multi-level transfer
			... fallthru common;
		}
		...
	}
	...
  common: // below fallthrough
		  // at case-clause level

The target label must be below the fallthrough and may not be nested in a control structure, and the target label must be at the same or higher level as the containing case clause and located at the same level as a case clause; the target label may be case default, but only associated with the current switch/choose statement.

Loop Control

Extend for / while / do-while iteration control to facilitate coding speed and safety.

Empty conditional implies comparison value of 1 (true).

while ( /*empty*/ )						// while ( true )
for ( /*empty*/ )						// for ( ; true; )
do ... while ( /*empty*/ )				// do ... while ( true )

The for control is extended with a range and step. A range is a set of values defined by an optional low value (default to 0), tilde, and high value, L ~ H, with an optional step ~ S (default to 1), which means an ascending set of values from L to H in positive steps of S.

0 ~ 5									// { 0, 1, 2, 3, 4, 5 }
-8 ~ -2 ~ 2								// { -8. -6, -4, -2 }
-3 ~ 3 ~ 1								// { -3, -2, -1, 0, 1, 2, 3 }

Warning: A range in descending order, e.g., 5 ~ -3 is the null (empty) set, i.e., no values in the set. Warning: A 0 or negative step is undefined. Note, the order of values in a set may not be the order the values are presented during looping.

The range character, '~', is decorated on the left and right to control how the set values are presented in the loop body. The range character can be prefixed with '+' or '-' indicating the direction the range is scanned, i.e., from left to right (ascending) or right to left (descending). If there is no prefix character, it defaults to '+'.

-8 ~ -2								// ascending, no prefix
0 +~ 5									// ascending, prefix
-3 -~ 3									// descending

For descending iteration, the L and H values are implicitly switched, and the increment/decrement for S is toggled. Warning: reversing the range endpoints for descending order results in an empty set.

for ( i; 10 -~ 1 )						// WRONG descending range!

Because C uses zero origin, most loops iterate from 0 to N - 1. Hence, when scanning a range during iteration, the last value is dropped, e.g., 0 ~ 5 is 0,1,2,3,4, an exclusive range, [L,H). To obtain all the values in the range, the range character is postfixed with '=', e.g., 0 ~= 5 is 0,1,2,3,4,5, an inclusive range, [L,H].

To access the value of the range iteration in the loop body, a loop index is specified before the range.

for ( int i; 0 ~ 10 ~ 2 ) { ... i ... }			// loop index available in loop body

The index type is optional (like C++ auto), where the type is normally inferred from the low value L because it initializes the index (the type of H can be different from L). When L is omitted, the type of the required high value H is used, as both L and H are the same type in this case.

for ( i; 1.5 ~ 5 )						// typeof(1.5) i; 1.5 is low value
for ( i; 5.5 )							// typeof(5.5) i; 5.5 is high value

The following examples illustrate common \CFA for-control combinations, with the C counter-part in the comment.

There are situations when the for-control actions need to be moved into the loop body, e.g., a mid-loop exit does not need an iteration-completion test in the for control. The character '@' indicates that a specific for-control action is ignored, i.e., generates no code.

for ( i; @ -~ 10 )						// for ( typeof(10) i = 10; /*empty*/; i -= 1 )
for ( i; 1 ~ @ ~ 2 )					// for ( typeof(1) i = 1; /* empty */; i += 2 )
for ( i; 1 ~ 10 ~ @ )					// for ( typeof(1) i = 1; i < 10; /* empty */ )
for ( i; 1 ~ @ ~ @ )					// for ( typeof(1) i = 1; /* empty */; /* empty */ )

Warning: L cannot be elided for the ascending range, @ ~ 5, nor H for the descending range, 1 -~ @, as the loop index is uninitialized. Warning: H cannot be elided in an anonymous loop index, 1 ~ @, as there is no index to stop the loop.

There are situations when multiple loop indexes are required. The character ':' means add another index, where any number of indices may be chained in a single for control.

for ( i; 5  :  j; 2 ~ 12 ~ 3 )			// for ( typeof(i) i = 1, j = 2; i < 5 && j < 12}; i += 1, j += 3 )
for ( i; 5  :  j; 2 ~ @ ~ 3 )			// for ( typeof(i) i = 1, j = 2; i < 5; i += 1, j += 3 )
for ( i; 5  :  j; 2.5 ~ @ ~ 3.5 )		// no C equivalent, without hoisting declaration of floating-point j

The following show more complex loop-control examples across all the different options.

while () { sout | "empty"; break; }
do { sout | "empty"; break; } while ();
for () { sout | "empty"; break; }				sout | nl | nlOff;

for ( 0 ) { sout | "A"; } sout | "zero";		sout | nl;
for ( 1 ) { sout | "A"; }						sout | nl;
for ( 10 ) { sout | "A"; }						sout | nl;
for ( ~= 10 ) { sout | "A"; }					sout | nl;
for ( 1 ~= 10 ~ 2 ) { sout | "B"; }				sout | nl;
for ( 1 -~= 10 ~ 2 ) { sout | "C"; }			sout | nl;
for ( 0.5 ~ 5.5 ) { sout | "D"; }				sout | nl;
for ( 0.5 -~ 5.5 ) { sout | "E"; }				sout | nl | nl;

for ( i; 10 ) { sout | i; }						sout | nl;
for ( i; ~= 10 ) { sout | i; }					sout | nl;
for ( i; 1 ~= 10 ~ 2 ) { sout | i; }			sout | nl;
for ( i; 1 -~= 10 ~ 2 ) { sout | i; }			sout | nl;
for ( i; 0.5 ~ 5.5 ) { sout | i; }				sout | nl;
for ( i; 0.5 -~ 5.5 ) { sout | i; }				sout | nl;
for ( ui; 2u ~= 10u ~ 2u ) { sout | ui; }		sout | nl;
for ( ui; 2u -~= 10u ~ 2u ) { sout | ui; }		sout | nl | nl | nl;

enum { N = 10 };
for ( N ) { sout | "N"; }						sout | nl;
for ( i; N ) { sout | i; }						sout | nl;
for ( i; -~ N ) { sout | i; }					sout | nl | nl | nl;

const int low = 3, high = 10, inc = 2;
for ( i; low ~ high ~ inc + 1 ) { sout | i; } sout | nl | nl;

for ( i; 1 ~ @ ) { if ( i > 10 ) break; sout | i; } sout | nl;
for ( i; 2.1 ~ @ ~ @ ) { if ( i > 10.5 ) break; sout | i; i += 1.7; } sout | nl;
for ( i; 5 : j; -5 ~ @ ) { sout | i | j; }		sout | nl;
empty
empty
empty

zero
A
A A A A A A A A A A
A A A A A A A A A A A
B B B B B
C C C C C
D D D D D
E E E E E

0 1 2 3 4 5 6 7 8 9
0 1 2 3 4 5 6 7 8 9 10
1 3 5 7 9
10 8 6 4 2
0.5 1.5 2.5 3.5 4.5
5.5 4.5 3.5 2.5 1.5
2 4 6 8 10
10 8 6 4 2


N N N N N N N N N N
0 1 2 3 4 5 6 7 8 9
10 9 8 7 6 5 4 3 2 1


3 6 9

1 2 3 4 5 6 7 8 9 10
2.1 3.8 5.5 7.2 8.9
0 -5 1 -4 2 -3 3 -2 4 -1

Loop Control Examples

Labelled break / continue

Extend break/continue with a target label to support static multi-level exit (like Java).

Compound: {
	Try: try {
		For: for ( ... ) {
			While: while ( ... ) {
				Do: do {
					If: if ( ... ) {
						Switch: switch ( ... ) {
							case 3:
								break Compound;
								break Try;
								break For;		/* or */	continue For;
								break While;	/* or */	continue While;
								break Do;		/* or */	continue Do;
								break If;
								break Switch;
							} // switch
						} else {
							... break If; ...	// terminate if
						} // if
				} while ( ... ); // do
			} // while
		} // for
	} finally { // always executed
	} // try
} // compound

Extended else

The if statement has an optional else clause executed if the conditional is false. This concept is extended to the while, for, and do looping constructs (like Python). Hence, if the loop conditional becomes false, looping stops and the corresponding else clause is executed, if present.

The following example is a linear search for the key 3 in an array, where finding the key is handled with a break and not finding with the else clause on the loop construct.

int a[10];

while ( int i = 0; i < 10 ) {
  if ( a[i] == 3 ) break; // found
	i += 1;
} else {
	// not found
}

	
for ( i; 10 ) {
  if ( a[i] == 3 ) break; // found

} else {
	// not found
}

int i = 0;
do {
  if ( a[i] == 3 ) break; // found
	i += 1;
} while( i < 10 ) else {
	// not found
}

Exception Handling

Exception handling provides dynamic name look-up and non-local transfer of control.

exception_t E {};						// exception type
void f(...) {
	... throw E{}; ...					// termination
	... throwResume E{}; ...			// resumption
}
try {
	f(...);
} catch( E e ; boolean-predicate ) {	// termination handler, optional predicate
	// recover and continue
} catchResume( E e ; boolean-predicate ) {	// resumption handler, optional predicate
	// repair and return
} finally {
	// always executed
}

with Clause / Statement

Open an aggregate scope making its fields directly accessible (like Pascal, but open in parallel).

struct S { int i; int j; double m; } s;	// field i has same type in structure types S and T
struct T { int i; int k; int m; } t;
with ( s, t ) {							// open structure variables s and t in parallel
	j + k;								// unambiguous, s.j + t.k
	m = 5.0;							// unambiguous, s.m = 5.0
	m = 1;								// unambiguous, t.m = 1
	int a = m;							// unambiguous, a = t.m
	double b = m;						// unambiguous, b = s.m
	int c = s.i + t.i;					// unambiguous with qualification
	(double)m;							// unambiguous with cast s.m
}

C∀'s ability to overload variables means fields with the same name but different types are automatically disambiguated, eliminating most qualification when opening multiple aggregates. Qualification or a cast is used to disambiguate.

mutex Statement

Local acquisition of monitor mutual exclusion to reduce refactoring and naming (like Java).

monitor ReadersWriter { ... };
void startRead( ReadersWriter & mutex rw ) {
	// critical section
}
void endRead( ReadersWriter & mutex rw ) {
	// critical section
}
void read( ReadersWriter & rw ) { // no mutex
	startRead( rw );
	// multiple readers
	endRead( rw );
}
		
void read( ReadersWriter & rw ) { // no mutex
	mutex( rw ) {		// start read
		// critical section
	}
	// multiple readers
	mutex( rw ) {		// end read
		// critical section
	}
}


As for monitors functions, a list of monitor objects is allowed and acquired without deadlock.

The special form of the mutex statement for an expression statement removes error-prone duplication by distributing the mutex property across the left-hand side of an assignment expression.

mutex( sout ) sout | ...;			// serialize printing
mutex( a, b, c ) [a, b, c] = ...	// acquire multiple kinds of locking objects without deadlock

waitfor Statement

Dynamic selection of calls to mutex type is controlled by the waitfor statement, which atomically blocks the calling thread, releases the monitor lock, and restricts the function calls that can next acquire mutual exclusion.

void main() {
	waitfor( r1 : c ) ...;
	waitfor( r1 : c ) ...; or waitfor( r2 : c ) ...;
	waitfor( r2 : c ) { ... } or timeout( 1 ) ...;
	waitfor( r3 : c1, c2 ) ...; or else ...;
	when( a > b ) waitfor( r4 : c ) ...; or when ( c > d ) timeout( 2 ) ...; or
		when ( c > 5 ) else ...;
	when( a > b ) waitfor( r5 : c1, c2 ) ...; or waitfor( r6 : c1 ) ...; or else ...;
	when( a > b ) waitfor( r7 : c ) ...; or waitfor( r8 : c ) ...; or timeout( 2 ) ...;
	when( a > b ) waitfor( r8 : c ) ...; or waitfor( r9 : c1, c2 ) ...; or
		 when ( c > d ) timeout( 1 ) ...; or else ...;
}

Specifically, a thread calling the monitor is unblocked directly from the calling queue based on function names that can fulfill the cooperation required by the signaller. (The linear search through the calling queue to locate a particular call can be reduced to O(1).) Hence, the waitfor has the same semantics as signal_block, where the signallee thread from the calling queue executes before the signaller, which waits on urgent. Now when a producer/consumer detects a full/empty buffer, the necessary cooperation for continuation is specified by indicating the next function call that can occur.


Overloading

Variables, functions, operators, and literals 0/1 may be overloaded.

Variable

Variable names within a block may be overloaded depending on type.

short int MAX = 32767;
int MAX = 2147483647;
double MAX = 1.797...357e+308L;
short int s = MAX;   // select MAX based on left-hand type
int i = MAX;
double d = MAX;

Function

Function names within a block may be overloaded depending on the number and type of parameters and returns.

// select based on type and number of parameters
void f( void );					// (1)
void f( char );					// (2)
void f( int, double );			// (3)
f();							// select (1)
f( 'a' );						// select (2)
f( 3, 5.2 );					// select (3)
// select based on type and number of returns
char f( int );						// (1)
double f( int );					// (2)
[int, double] f( int );				// (3)
char c = f( 3 );					// select (1)
double d = f( 4 );					// select (2)
[int, double] t = f( 5 );			// select (3)

Operator

Operator names within a block may be overloaded depending on the number and type of parameters and returns. An operator name is denoted with '?' for the operand and the standard C operator-symbol. Operators '&&', '||', and '?:' cannot be overloaded because the short-circuit semantics cannot be preserved.

int ++?( int & op );					// unary prefix increment
int ?++( int & op );					// unary postfix increment
int ?+?( int op1, int op2 );			// binary plus
int ?<=?( int op1, int op2 );			// binary less than
int ?=?( int & op1, int op2 );			// binary assignment
int ?+=?( int & op1, int op2 );			// binary plus-assignment

struct S { int i, j; };
S ?+?( S op1, S op2 ) {					// add two structures
	return (S){ op1.i + op2.i, op1.j + op2.j };
}
S s1 = { 1, 2 }, s2 = { 2, 3 }, s3;
s3 = s1 + s2;							// compute sum: s3 == { 2, 5 }

Extending Types

Existing C library-types can be augmented without inheritance. C++ can only partially extend C types as constructors, destructors, conversions and operators =, [], (), -> may only appear in a class.

#include <time.h>
void ?{}( timespec & t ) {}
void ?{}( timespec & t, time_t sec ) with(t) { tv_sec = sec; tv_nsec = 0; }
void ?{}( timespec & t, time_t sec, time_t nsec ) with(t) { tv_sec = sec; tv_nsec = nsec; }
void ?{}( timespec & t, zero_t ) with(t) { tv_sec = 0; tv_nsec = 0; }
timespec ?+?( timespec & lhs, timespec rhs ) with(lhs) {
	return (timespec)@{ tv_sec + rhs.tv_sec, tv_nsec + rhs.tv_nsec }; // @ ⇒ C-style initialization
}
_Bool ?==?( timespec lhs, timespec rhs ) with(lhs) {
	return tv_sec == rhs.tv_sec && tv_nsec == rhs.tv_nsec;
}
timespec ?=?( timespec & t, zero_t ) { return t{ 0 }; }

timespec tv0, tv1 = 0, tv2 = { 3 }, tv3 = { 5, 100000 };
tv0 = tv1;
tv1 = tv2 + tv3;
if ( tv2 == tv3 ) ...
tv3 = tv2 = 0;

Overloading Examples


Polymorphism

Functions and aggregate type may have multiple type parameters each with constraints. Aggregate types may have single type inheritance and multiple implementation inheritance.

Kinds

There are 5 kinds of polymorphism type.

  1. Opaque is an incomplete type or pointer/reference with an implicit pointer size and implicitly generated reference and dereference operations.
    forall( T & ) void f( T & p ) {		// incomplete type, no data alignment or size
        T & t;							// no constructor
        T & s = { p };					// pointer initialization
        &t = &p;						// pointer assignment
    	t = p;							// no data assignment
    }
    
  2. Sized is an incomplete type or pointer/reference with an implicit pointer and data size and implicitly generated reference and dereference operations.
    forall( T * ) void f( T * p ) {		// incomplete type, data alignment and size
        T * t;							// no constructor
        T * s = { p };					// pointer initialization
        t = p;							// pointer assignment
    	*t = *p;						// data assignment
    }
    forall( T * ) apply( void (*f)( T * ), unsigned size, T data[size] ) { // C style array
        for ( unsigned i = 0 ; i < size ; i += 1 ) {
            f( &data[index] );			// subscript computation needs sizeof(T)
        }
    }
    
  3. Object is a complete type with alignment/size and implicitly generated default/copy constructors, assignment, and destructor (similar to C++). An object type can be instantiated, initialized, assigned, and de-instantiated.
    forall( T ) void f( T p ) {			// complete type, data alignment and size
        T t;							// default constructor
        T s = { p };					// copy constructor
        t = p;							// assignment
    }									// destructor
    int i;								// complete type
    f( i );
    struct S { int f; } s;				// complete type
    f( s );
    forall( T ) struct G { T f; };		// generic type
    G(int) g;							// complete type
    f( g );
    
  4. Variadic is a type that accepts zero or more inferred type arguments (like C++ variadic template).
    forall( R, S ) struct pair {R r; S s;};
    forall( T *, Params ...				// parameter pack
    			| { void ?{}( T &, Params ); } )
    T * new( Params p ) {
    	return & (*(T *) malloc()){ p };
    }
    pair(int, char) * x = new( 42, '!' ); // argument pack
    
    The new function provides the combination of type-safe malloc with a constructor call, making it impossible to forget constructing dynamically allocated objects. This function provides the type safety of new in C++, without the need to specify the allocated type again, due to return-type inference.
  5. Dimension is a generic dimension size for a generalized array.
    forall( T, [N] ) struct array {		// => element type, dimension
    	// use T and [N]
    };
    array( int, 5 ) arr;				// int arr[5]
    array( double, 100 ) arr;			// double arr[100]
    int i = arr[3];						// subscript checking
    double d = arr[3];
    
    forall( T, [N] ) void f( array( T, N ) x, array( T, N ) y ) { ... }
    forall( T, [N], [M] ) void g( array( T, N ) x, array( T, M ) y ) { ... }
    forall( T, S, [N], [M] ) void h( array( T, N ) x, array( S, M ) y ) { ... }
    ...
    f( a, b );							// a,b same type and dimension
    g( a, b );							// a,b same type and different dimensions
    h( a, b );							// a,b different types and dimensions
    // length accessible at runtime in f, g, and h (like Java .length).
    int pivot = N / 2;
    for ( i; M ) sout | a[i];
    

Trait

Named collection of constraints, both functions and variables.

forall( T )
trait sumable {
	void ?{}( T &, zero_t );			// constructor from 0 literal
	T ?+?( T, T );						// assortment of additions
	T ?+=?( T &, T );
	T ++?( T & );
	T ?++( T & );
};

Function

Functions may have multiple type parameters each with constraints.

forall( T | sumable( T ) )				// polymorphic, use trait
T sum( T a[ ], size_t size ) {
	T total = 0;						// instantiate T from 0 by calling its constructor
	for ( i; size ) total += a[i];		// select appropriate +
	return total;
}
int sa[ 5 ];
int i = sum( sa, 5 );					// use int 0 and +=

Polymorphic Function Examples

Type

Aggregate types may have multiple type parameters each with constraints.

forall( T | sumable( T ) ) {			// forall distribution
	struct TwoArrays {					// polymorphic (generic) type
		* T x, y;						// two C arrays, new declaration syntax
	};
	void ?{}( TwoArrays( T ) & ta, int size ) with ( ta ) { // constructor
		x = alloc( size ); y = alloc( size );
	}
	void ^?{}( TwoArrays( T ) & ta ) with ( ta ) { // destructor
		free( x ); free( y );
	}
}
int main() {
	enum { size = 5 };
	TwoArrays( int ) tai = { size };
	TwoArrays( double ) tad = { size };
	for ( i; size ) { tai.x[i] = i; tad.y[i] = i; }	// initialize
	sout | sum( tai.x, size ) | sum( tad.y, size );	// select int and double sum
}

Polymorphic Type Examples

Inheritance

Multiple implementation/nominal inheritance based on Plan-9 C. No down-casting from derived to base type.

struct S0 { int i, j; };
struct S1 { int i, j; };
struct S2 {
	inline S1;							// implementation/nominal inheritance
	int i, j;
	inline S0;							// implementation/nominal inheritance
};
void f1( S0 s1 ) with(s1) { sout | i | j; }
void f2( S1 s0 ) with(s0) { sout | i | j; }
void f3( S2 s2 ) {
	sout | s2.i | s2.j;
	S0 s0 = s2;							// (S0)s2
	with(s0) { sout | i | j; }
	S1 & s1 = s2;						// (S1)s2
	with(s1) { sout | i | j; }
}
int main() {
	//           S1     i, j     S0
	S2 s2 = { { 1, 2 }, 3, 4, { 5, 6 } };
	f1( s2 );							// (S0)s2
	f2( s2 );							// (S1)s2
	f3( s2 );
}
$ a.out
5 6
1 2
3 4
5 6
1 2

Auto Type-Inferencing

Auto type-inferencing occurs in a declaration where a variable's type is inferred from its initialization expression type.


auto j = 3.0 * 4;
int i;
auto k = i;
#define expr 3.0 * i
typeof(expr) j = expr;		// use type of initialization expression
int i;
typeof(i) k = i;			// use type of primary variable

The two important capabilities are:

  1. not determining or writing long generic types,
  2. ensuring secondary variables, related to a primary variable, always have the same type.

In C∀, typedef provides a mechanism to alias long type names with short ones, both globally and locally, but not eliminate the use of the short name. gcc provides typeof to declare a secondary variable from a primary variable. C∀ also relies heavily on the specification of the left-hand side of assignment for type inferencing, so in many cases it is crucial to specify the type of the left-hand side to select the correct type of the right-hand expression. Only for overloaded functions with the same return type is variable type-inferencing possible.

Finally, auto is NOT the same as typeof. auto presents the programming problem of tracking down the auto type. For example, given

auto j = ...

and the need to write a function to compute using j

void rtn( ... parm );
rtn( j );

A programmer must somehow work backwards to determine the type of j's initialization expression, reconstructing an arbitrary number of possibly long generic type-name. In this situation, having the type name, short alias, or a typeof provides a path back to the type name needed to declare the function parameter.

There is also the conundrum in type inferencing of when to brand a type. That is, when is the type of the variable more important than the type of its initialization expression. For example, if a change is made in an initialization expression, it can cause cascading type changes and/or errors. At some point, a variable type needs to remain constant and the expression to be in error when it changes.

Given typedef and typeof in C∀, and the strong need to use the type of the left-hand side in inferencing, general auto type-inferencing is not supported at this time. Should a significant need arise, this feature can be revisited.


Typed Control Flow

Generators, coroutines, monitors, and threads provide advanced typed control-flow, similar to typed control-flow in μC++.

Generator

Stackless asymmetric and symmetric generators allow retaining data and execution state between calls. Unlike asynchronous await, C∀ generators are programmer scheduled. State is only retained in the generator type but not the generator's main function.

generator Fib {
	int fn1, fn;
};
void main( Fib & b ) with(b) {
	[fn1, fn] = [1, 0];
	for () {
		suspend;
		[fn1, fn] = [fn, fn + fn1];
	}
}
int main() {
	Fib f1, f2;
	for ( 10 ) {
		resume( f1 );
		resume( f2 );
		sout | f1.fn | f2.fn;
	}
}

Coroutine

Stackful asymmetric and symmetric coroutines allow retaining data and execution state between calls. Unlike asynchronous await, C∀ coroutines are programmer scheduled. State is retained in both the coroutine type and the coroutine's main function.

#include <fstream.hfa>
#include <coroutine.hfa>

// match left/right parenthesis: ((())) match, (() mismatch

enum Status { Cont, Match, Mismatch };
coroutine CntParens {
	char ch;											// used for communication
	Status status;
};
void main( CntParens & cpns ) with( cpns ) {			// coroutine main
	unsigned int cnt = 0;
	for ( ; ch == '('; cnt += 1 ) suspend;				// count left parenthesis
	for ( ; ch == ')' && cnt > 1; cnt -= 1 ) suspend;	// count right parenthesis
	status = ch == ')' ? Match : Mismatch;
}
void ?{}( CntParens & cpns ) with( cpns ) { status = Cont; }
Status next( CntParens & cpns, char c ) with( cpns ) {	// coroutine interface
	ch = c;
	resume( cpns );
	return status;
}
int main() {
	CntParens cpns;
	char ch;

	for () {											// read until end of file
		sin | ch;										// read one character
	  if ( eof( sin ) ) { sout | "Mismatch"; break; }	// eof ?
		Status ret = next( cpns, ch );					// push character for checking
	  if ( ret == Match ) { sout | "Match"; break; }
	  if ( ret == Mismatch ) { sout | "Mismatch"; break; }
	}
}

Coroutine Examples

Monitor

A monitor type defines data protected with mutual exclusion, and the mutex qualifier acquires mutual exclusion. Bulk acquisition of multiple monitors is supported.

Bank transfer problem, where two resources must be locked simultaneously.

C∀C++
#include <thread.hfa>



monitor BankAccount {

	int balance;
} b1 = { 0 }, b2 = { 0 };
void deposit( BankAccount & mutex b, int deposit ) with(b) {

	balance += deposit;
}
void transfer( BankAccount & mutex my,
				BankAccount & mutex your, int me2you ) {

	deposit( my, -me2you ); // debit
	deposit( your, me2you ); // credit
}
thread Person { BankAccount & b1, & b2; };
void main( Person & person ) with(person) {
	for ( 10_000_000 ) {
		if ( random() % 3 ) deposit( b1, 3 );
		if ( random() % 3 ) transfer( b1, b2, 7 );
	}
}
int main() {
	Person p1 = { b1, b2 }, p2 = { b2, b1 };


} // wait for threads to complete
#include <thread>
#include <mutex>
using namespace std;

struct BankAccount {
	recursive_mutex m; // must be recursive
	int balance = 0;
} b1, b2;
void deposit( BankAccount & b, int deposit ) {
	scoped_lock lock( b.m );
	b.balance += deposit;
}
void transfer( BankAccount & my,
			BankAccount & your, int me2you ) {
	scoped_lock lock( my.m, your.m );
	deposit( my, -me2you ); // debit
	deposit( your, me2you ); // credit
}

void person( BankAccount & b1, BankAccount & b2 ) {
	for ( int i = 0; i < 10'000'000; i += 1 ) {
		if ( random() % 3 ) deposit( b1, 3 );
		if ( random() % 3 ) transfer( b1, b2, 7 );
	}
}
int main() {
	thread p1( person, ref(b1), ref(b2) ),
		   p2( person, ref(b2), ref(b1) );
	p1.join(); p2.join();
}

Monitor Examples

Thread

A thread type, T, instance creates a user-level thread, which starts running in function void main( T & ), and joins on deallocation.

#include <fstream.hfa>
#include <thread.hfa>
thread T {
	int id;
};
void ?{}( T & t ) { t.id = 0; }
void ?{}( T & t, int id ) { t.id = id; }
void main( T & t ) with( t ) {			// thread starts here
	sout | id;
}
int main() {
	enum { NumThreads = 5 };
	T t[ NumThreads ];					// create/start threads
	T * tp[ NumThreads ];
	for ( i; NumThreads ) {
		tp[i] = new( i + 1 );			// create/start threads
	}
	for ( i; NumThreads ) {
		delete( tp[i] );				// wait for thread to terminate
	}
}										// wait for threads to terminate

Thread Examples


Libraries

Stream I/O

C∀ provides polymorphic stream I/O via stdin/sin (input), and stdout/sout and stderr/serr (output) (like C++ cin/cout/cerr) with implicit separation and newline (like Python).

#include <fstream.hfa>					// C∀ stream I/O
char c;  int i;  double d;
sin  | c | i | d;						// input format depends on variable type: x  27  2.3
sout | c | i | d;						// output format depends on constant/variable type
x 27 2.3								// implicit separation between values and auto newline

[int, [ int, int ] ] t1 = [ 1, [ 2, 3 ] ], t2 = [ 4, [ 5, 6 ] ];
sout | t1 | t2;							// print tuples
1, 2, 3 4, 5, 6

Polymorphic streams exit and abort provide implicit program termination without and with generating a stack trace and core file. Stream exit implicitly returns EXIT_FAILURE to the shell.

exit  | "x (" | x | ") has negative value."; // terminate and return EXIT_FAILURE to shell
abort | "x (" | x | ") has negative value."; // terminate and generate stack trace and core file

Note, C∀ stream variables stdin, stdout, stderr, exit, and abort overload C variables stdin, stdout, stderr, and functions exit and abort, respectively.

The following example is idiomatic C∀ command-line processing and copying an input file to an output file. Note, a stream variable may be copied because it is a reference to an underlying stream data-structures. All unusual I/O cases are handled as exceptions, including end-of-file.

#include <fstream.hfa>

int main( int argc, char * argv[] ) {
	ifstream in  = stdin;							// copy default files
	ofstream out = stdout;

	try {
		choose ( argc ) {
		  case 3, 2:
			open( in, argv[1] );					// open input file first as output creates file
			if ( argc == 3 ) open( out, argv[2] );	// do not create output unless input opens
		  case 1: ;									// use default files
		  default:
			exit | "Usage" | argv[0] | "[ input-file (default stdin) [ output-file (default stdout) ] ]";
		} // choose
	} catch( open_failure * ex; ex->istream == &in ) { // input file errors
		exit | "Unable to open input file" | argv[1];
	} catch( open_failure * ex; ex->ostream == &out ) { // output file errors
		close( in );								// optional
		exit | "Unable to open output file" | argv[2];
	} // try

	out | nlOff;									// turn off auto newline
	in  | nlOn;										// turn on reading newline

	char ch;
	try {
		for () {									// read/write characters
			in | ch;
			out | ch;
		} // for
	} catch( end_of_file * ) {						// end-of-file raised
	} // try
} // main

Stream Examples

String Stream

The stream types ostrstream and istrstream provide all the stream formatting capabilities to/from a C string rather than a stream file. The only string stream operations different from a file stream are:

String Stream Examples

Pseudo Random Number Generator

There is a sequential PRNG type, only accessible by a single thread (not thread-safe), and a set of global and companion thread PRNG functions, accessible by multiple threads without contention.

GMP

Interface to GMP multi-precise library through type Int, e.g., compute first 41 factorials with complete accuracy.

C∀C
#include <gmp.hfa>				// provide type Int
int main( void ) {
	sout | "Factorial Numbers";
	Int fact = 1;				// multi-precise integer
	sout | 0 | fact;			// special case for 0!
	for ( i; 1 ~= 40 ) {
			fact *= i;
			sout | i | fact;
	}
}
#include <gmp.h>
int main( void ) {
	gmp_printf( "Factorial Numbers\n" );
	mpz_t fact;  mpz_init_set_ui( fact, 1 );
	gmp_printf( "%d %Zd\n", 0, fact );
	for ( unsigned int i = 1; i <= 40; i += 1 ) {
		mpz_mul_ui( fact, fact, i );
		gmp_printf( "%d %Zd\n", i, fact );
	}
}

Large constants are created using numerical strings.

Int x = { "50000000000000000000" };
x = "12345678901234567890123456789"`mp + "12345678901234567890123456789"`mp;

Miscellaneous

Backquote Identifiers

Keywords as identifier to deal with new keyword clashes in legacy code.

int ``int, ``forall;					// keywords as identifiers
``forall = ``int = 5;
``int += 7;

Exponentiation Operator

New binary exponentiation operator x \ y (backslash) for integral and floating-point types. Multiplication is O(log y). Overflow for a large exponent or negative exponent returns zero.

1 \ 0;									// integral result, 1 convention
1 \ 1;									// integral result, 1
2 \ 8;									// integral result (shifting), 256
-4 \ 3;									// integral result (multiplication), -64
5 \ 3;									// integral result (multiplication), 125
5 \ 32;									// integral result (multiplication), 0 overflow
5L \ 32;								// integral result (multiplication), 3273344365508751233
5L \ 64;								// integral result (multiplication), 0 overflow
-4 \ -3;								// integral result (multiplication), 0 reciprocal fraction
-4.0 \ -3;								// floating-point result (logarithm), -0.015625 reciprocal fraction
4.0 \ 2.1;								// floating-point result (logarithm), 18.3791736799526
(1.0f+2.0fi) \ (3.0f+2.0fi);			// complex floating-point result (logarithm), 0.264715-1.1922i

Remove Definition Keyword

Keywords struct and enum are not required in a definition (like C++).

struct S { ... };
enum E { ... };
S s;									// "struct" before S unnecessary
E e;									// "enum" before E unnecessary

char Types

char, signed char, and unsigned char are distinct types and may be overloaded.

#include <fstream.hfa>
int main() {
	char c = 'a';
	signed char sc = 'a';
	unsigned char uc = 'a';
	sout | c | sc | uc;					// prints a97 97
}

int128 Type

New basic overloadable type for 128-bit integers.

int main() {
	int128 wi = 1;
	unsigned int128 uwi = 2;
	wi += uwi;
}

basetypeof Type

Pseudo function like typeof returning the base type of a type or variable.

basetypeof( const unsigned int ) usi;	// unsigned int
volatile const signed char ch;
basetypeof( ch ) sc;					// signed char
enum { N = 3 };
basetypeof( N ) si;						// signed int

Keywords

C∀ introduces the following new keywords, which cannot be used as identifiers without backquotes.

basetypeof
choose
coroutine
disable
enable
exception
fallthrough
fallthru
finally
fixup
forall
generator
int128
monitor
mutex
one_t
report
suspend
throw
throwResume
trait
try
virtual
waitfor
when
with
zero_t

C∀ introduces the following new quasi-keywords, which can be used as identifiers.

catch
catchResume
finally
fixup
or
timeout