Features

Quick tour of C∀ features. What makes C∀ better!


Declarations

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

Alternative Declaration Syntax

Left-to-right declaration syntax, except bit fields.

C∀C
* int p;
[5] int a;
* [5] int pa;
[5] * int ap;
* int p1, p2;
const * const int cpc;
const * [ 5 ] const int cpac;
extern [ 5 ] int xa;
static * const int sp;
* [ int ] ( int ) fp;
* [ * [ ] 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] );

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 deferences (&*)**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

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 = 10;  data = alloc( size );
}
void ^?{}( VLA & vla ) with ( vla ) {	// destructor
	free( data );
}
void ?{}( VLA & vla, int size, char fill = '\0' ) { // initialization
	vla.[ size, data ] = [ size, alloc( size, fill ) ];
}
void ?{}( VLA & vla, VLA other ) {		// copy, shallow
	vla = other;
}
{
	VLA  x,		y = { 20, 0x01 },	z = y;	// z points to y
	//   x{};	y{ 20, 0x01 };		z{ z, y };
	^x{};								// deallocate x
	x{};								// reallocate x
	z{ 5, 0xff };						// reallocate z, not pointing to y
	^y{};								// deallocate y
	y{ x };								// reallocate y, points to x
	x{};								// reallocate x, not pointing to y
}	//  ^z{};  ^y{};  ^x{};

Nested Routines

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

forall( otype 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.

Qualifier Distribution

To reduce duplication, forall and storage-class qualifiers may be distributed over a group of functions/types,

forall( otype T ) {						// distribution block, add forall qualifier to declarations
	struct stack { stack_node(T) * head; };	// generic type
	inline {							// nested distribution block, add forall/inline to declarations
		void push( stack(T) & s, T value ) ...
		// generic operations
	}
}

Literals

Underscore Separator

Numeric literals allow underscores.

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, and z for size_t. New length suffixes for 8, 16, 32, 64, and 128 bit integers.

20_hh			// signed char
21_hhu			// unsigned char
22_h			// signed short int
23_uh			// unsigned short int
24z				// size_t
20_L8			// int8_t
21_ul8			// uint8_t
22_l16			// int16_t
23_ul16			// uint16_t
24_l32			// int32_t
25_ul32			// uint32_t
26_l64			// int64_t
27_l64u			// uint64_t
26_L128			// int128
27_L128u		// unsigned int128

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;
	}
}

Postfix Function/Call

Alternative call syntax (postfix: literal argument before routine name) to convert basic literals into user literals, where ?` denotes a postfix-function name and ` denotes a postfix-function call..

postfix functionconstant argument callvariable argument callpostfix routine 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;

Postfix Call Examples


Overloading

Variables, routines, 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.79769313486231570814527423731704357e+308L;
short int s = MAX;   // select MAX based on left-hand type
int i = MAX;
double d = MAX;

Routine

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

// selection 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)
// selection 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; object-oriented languages require 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

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

Trait

Named collection of constraints, both functions and variables.

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

Polymorphic Routine

Routines may have multiple type parameters each with constraints.

forall( otype 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 Routine Examples

Polymorphic Type

Aggregate types may have multiple type parameters each with constraints.

forall( otype 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 Type

Single inheritance based on Plan-9 C. Must be first field for type inheritance; otherwise implementation inheritance.

struct B { int i; };
struct D {
	inline B;  							// inherit type and fields, initialize
	int j;
};
struct E {
	inline D;  							// inherit type and fields, initialize
	int k;
};

D fb( B b ) {  							// by value
	b.i = 3;
	return b;  							// contra-variance, truncation
}
D & gb( B & b ) {  						// by reference
	b.i = 3;
	return b;  							// contra-variance, no truncation
}
E fd( D d ) {  							// by value
	d.j = 3;
	return d;  							// contra-variance, truncation
}
E & gd( D & d ) {  						// by reference
	d.j = 3;
	return d;  							// contra-variance, no truncation
}

int main() {
	D d;
	d.i = 5; d.j = 7;
	d = fb( d );						// lose j, truncation
	sout | d.i | d.j;
	d.j = 8;							// reset j
	d = gb( d );
	sout | d.i | d.j;

	E e;
	e.i = 5; e.j = 7; e.k = 13;
	e = fd( e );						// lose k, truncation
	sout | e.i | e.j | e.k;
	e.k = 18;							// reset k
	e = gd( e );
	sout | e.i | e.j | e.k;
}

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 routines with the same return type is variable type-inferencing possible. Finally, auto presents the programming problem of tracking down a type when the type is actually needed. For example, given

auto j = ...

and the need to write a routine to compute using j

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

A programmer must work backwards to determine the type of j's initialization expression, reconstructing the possibly long generic type-name. In this situation, having the type name or a short alias is very useful.

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 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.


Coroutines / Concurrency

Coroutines, monitors, and threads provides advanced control-flow, similar to μC++.

Coroutine

Stackfull semi and full coroutines allow retaining data and execution state between calls.

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

// match left/right parenthesis

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

Status next( CntParens & cpns, char c ) with( cpns ) {
	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 | "Error"; break; }		// eof ?
		Status ret = next( cpns, ch );					// push character for formatting
	  if ( ret == Match ) { sout | "Match"; break; }
	  if ( ret == Error ) { sout | "Error"; 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>
#include <fstream.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 <mutex>
#include <thread>
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 routine 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


Control Structures

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 fall through to be non-terminating in case clause or have target label providing common code.

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 for 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.

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

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

for ( i; 10 ) { sout | i; }					sout | nl;
for ( i; 1 ~= 10 ~ 2 ) { sout | i; }		sout | nl;
for ( i; 10 -~= 1 ~ 2 ) { sout | i; }		sout | nl;
for ( i; 0.5 ~ 5.5 ) { sout | i; }			sout | nl;
for ( i; 5.5 -~ 0.5 ) { sout | i; }			sout | nl;
for ( ui; 2u ~= 10u ~ 2u ) { sout | ui; }	sout | nl;
for ( ui; 10u -~= 2u ~ 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 -~ 0 ) { sout | i; }				sout | nl | nl;

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

zero
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
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

Loop Control Examples

Labelled break / continue

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

LC: {
	... declarations ...
	LS: switch ( ... ) {
		case 3:
			LIF: if ( ... ) {
				LF: for ( ... ) {
					LW: while ( ... ) {
						... break LC; ...		// terminate compound
						... break LS; ...		// terminate switch
						... break LIF; ...		// terminate if
						... continue LF; ...	// continue loop
						... break LF; ...		// terminate loop
						... continue LW; ...	// continue loop
						... break LW; ...		// terminate loop
					} // while
				} // for
			} else {
				... break LIF; ...				// terminate if
			} // if
	} // switch
} // compound

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
	// recover and continue
} catchResume( E e ; boolean-predicate ) {	// resumption handler
	// repair and return
} finally {
	// always executed
}

with Clause/Statement

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

struct S { int i; int j; double m; } s;	// member 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, qualification
	(double)m;							// unambiguous, cast s.m
}

C∀'s ability to overload variables means members 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.

waitfor Statement

Dynamic selection of calls to mutex type.

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 ) ...; when ( c > 5 ) or 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 ...;
}

Libraries

Stream I/O

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 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. Note, stream variable names exit and abort overload function names exit and abort.

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

Idiomic C∀ copy input file to output file.

#include <fstream.hfa>
#include <stdlib.hfa>					// new/delete

int main( int argc, char * argv[] ) {
	ifstream * in  = &stdin;			// default files
	ofstream * out = &stdout;
	try {
		choose ( argc ) {
		  case 2, 3:
			  in = new( argv[1] );		// open input file first as output creates file
			  if ( argc == 3 ) out = new( argv[2] ); // only open output if input opens as output created if nonexistent
		  case 1: ;						// use default files
		  default:
			  exit | "Usage [ input-file (default stdin) [ output-file (default stdout) ] ]";
		}
		char ch;
		*out | nlOff;					// turn off auto newline
		*in  | nlOn;					// turn on reading newline
		for () {						// read all characters
			*in | ch;
		  if ( eof( *in ) ) break;		// eof ?
			*out | ch;
		}
	} finally {							// always executed
		if ( in  != &stdin  ) delete( in );	// close file, do not delete stdin!
		if ( out != &stdout ) delete( out ); // close file, do not delete stdout!
	}
}

Stream Examples

GMP

Interface to GMP multi-precise library through type Int, e.g., compute first 40 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;
	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 );
	}
}

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).

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 routine 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