Features

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


Control Structures

if Conditional

Extend if conditional with declaration, similar to for conditional.

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 and y local to if/else 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 only at start, local to switch body
  case 0:
	...
	int j = 0;                                     // disallow unsafe initialization
  case 1:
	{
		int k = 0;                              // allow at different nesting levels
		...
	  case 2:                                    // disallow case in nested statements
	}
  ...
}

choose Statement

Alternative switch statement with default break from a case clause.

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

Labelled continue / break

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

with Clause/Statement

Open an aggregate scope making its fields directly accessible (like Pascal).

struct S { int i, j; };
int mem( S & this ) with( this ) {       // with clause
	i = 1;                                          // this->i
	j = 2;                                          // this->j
}
int foo() {
	struct S1 { ... } s1;
	struct S2 { ... } s2;
	with( s1 ) {                                 // with statement
		// access fields of s1 without qualification
		with( s2 ) {                           // nesting
			// access fields of s1 and s2 without qualification
		}
	}
	with( s1, s2 ) {                           // scopes open in parallel
		// access unambiguous fields of s1 and s2 without qualification
	}
}

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

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
}

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
}

Alternative Declaration Syntax

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

* int p;                                           // int * p;
[5] int a;                                        // int a[5];
* [5] int pa;                                    // int (* pa)[5];
[5] * int ap;                                    // int * ap[5];
* int p1, p2;                                   // int * p1, * p2;
const * const int cpc;                    // const int * const cpc;
const * [ 5 ] const int cpac;           // const int * const cpac[5]
extern [ 5 ] int xa;                         // extern int xa[5]
static * const int sp;                      // static const int * sp;
* [ int ] ( int ) fp;                            // int (* fp)( int )
* [ * [ ] int ] ( int ) gp;                     // int (* (* gp)( int ))[ ];
[5] * [ * [ int ] (int) ] ( int ) hp;         // int (* (* hp[5])( int ))( int );
(* int)x;                                         // (int *)x
sizeof( [ 5 ] * int );                         // sizeof( * int [5] );

References

Rebindable references providing multiple dereferencing. Use when value is accessed more than address.

int x, *p1 = &x, **p2 = &p1, ***p3 = &p2,
	&r1 = x,    &&r2 = r1,   &&&r3 = r2;
***p3 = 3;                                      // change x
r3 = 3;                                          // change x, ***r3
**p3 = ...;                                      // change p1
&r3 = ...;                                       // change r1, (&*)**r3
*p3 = ...;                                       // change p2
&&r3 = ...;                                     // change r2, (&(&*)*)*r3
&&&r3 = p3;                                  // change r3 to p3, (&(&(&*)*)*)r3
int y, z, & ar[3] = { x, y, z };           // initialize array of references
&ar[1] = &z;                                  // change reference array element
typeof( ar[1] ) p;                           // is int, i.e., the type of referenced object
typeof( &ar[1] ) q;                        // is int &, i.e., the type of reference
sizeof( ar[1] ) == sizeof( int );       // is true, i.e., the size of referenced object
sizeof( &ar[1] ) == sizeof( int *);   // is true, i.e., the size of a reference

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. A calls to a constructor/destructor is generated implicitly by the compiler or explicitly by the programmer.

#include <stdlib>
struct S {
	size_t size;
	int * ia;
};
void ?{}( S & s, int asize ) with( s ) { // constructor operator
	size = asize;                           // initialize fields
	ia = calloc( size );                   // polymorphic calloc
}
void ^?{}( S & s ) with( s ) {          // destructor operator
	free( ia );                                // de-initialization fields
}
int main() {
	S x = { 10 }, y = { 100 };         // implict calls: ?{}( x, 10 ), ?{}( y, 100 )
	...                                           // use x and y
	^x{};  ^y{};                              // explicit calls to de-initialize
	x{ 20 };  y{ 200 };                   // explicit calls to reinitialize
	...                                           // reuse x and y
}                                                   // implict calls: ^?{}( y ), ^?{}( x )

Nested Routines

Nested routines provide a local mechanism to override ...

forall( otype T | { int ?<?( T, T ); } ) T * bsearch( T key, const T * arr, size_t size ) {
	int comp( const void * t1, const void * t2 ) { /* as above with double changed to T */ }
	return (T *)bsearch( &key, arr, size, sizeof(T), comp ); }
forall( otype T | { int ?<?( T, T ); } ) unsigned int bsearch( T key, const T * arr, size_t size ) {
	T * result = bsearch( key, arr, size );	// call first version
	return result ? result - arr : size; }	// pointer subtraction includes sizeof(T)

double * val = bsearch( 5.0, vals, 10 );	// selection based on return type
int posn = bsearch( 5.0, vals, 10 );

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.

f( 20_hh );                                      // void f( signed char )
f( 21_hhu );                                    // void f( unsigned char )
f( 22_h );                                        // void f( signed short int )
f( 23_uh );                                      // void f( unsigned short int )
f( 24z );                                          // void f( size_t )

f( 20_L8 );                                      // void f( int8_t )
f( 21_ul8 );                                     // void f( uint8_t )
f( 22_l16 );                                     // void f( int16_t )
f( 23_ul16 );                                   // void f( uint16_t )
f( 24_l32 );                                     // void f( int32_t )
f( 25_ul32 );                                   // void f( uint32_t )
f( 26_l64 );                                     // void f( int64_t )
f( 27_l64u );                                   // void f( uint64_t )
f( 26_L128 );                                  // void f( int128 )
f( 27_L128u );                                // void f( 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 required
void ?{}( S * s, one_t ) with( s ) { i = j = 1; }   // one_t, no parameter name required
int ?!=?( S * op1, S * op2 ) { return op1->i != op2->i || op1->j != op2->j; }
S ?+?( S op1, S op2 ) { return ?{}( op1->i + op2->i, op1->j + op2->j; }

S s0 = { 0, 1 }, s1 = { 3, 4 };           // implict call: ?{}( s0, zero_t ), ?{}( s1, one_t )
if ( s0 )                                           // rewrite: s != 0 ⇒ S temp = { 0 }; ?!=?( s, temp )
	 s0 = s0 + 1;                            // rewrite: S temp = { 1 }; ?+?( s0, temp ); 

Units

Alternative call syntax (literal argument before routine name) to convert basic literals into user literals.

struct Weight { double stones; };

void ?{}( Weight & w ) { w.stones = 0; }                        // operations
void ?{}( Weight & w, double w ) { w.stones = w; }
Weight ?+?( Weight l, Weight r ) { return (Weight){ l.stones + r.stones }; }

Weight ?`st( double w ) { return (Weight){ w }; }         // backquote for units
Weight ?`lb( double w ) { return (Weight){ w / 14.0 }; }
Weight ?`kg( double w ) { return (Weight) { w * 0.1575}; }

int main() {
	Weight w, hw = { 14 };            // 14 stone
	w = 11`st + 1`lb;
	w = 70.3`kg;
	w = 155`lb;
	w = 0x_9b_u`lb;                     // hexadecimal unsigned weight (155)
	w = 0_233`lb;                         // octal weight (155)
	w = 5`st + 8`kg + 25`lb + hw;
}

Overloading

Routines, operators, variables, and literals 0/1 may be overloaded.

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.

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 }

Variable

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

short int MAX = ...;   int MAX = ...;  double MAX = ...;
short int s = MAX;    int i = MAX;    double d = MAX;   // select MAX based on left-hand type

Parametric Polymorphism

Routines and aggregate type may have multiple type parameters each with constraints.

Trait

Named collection of constraints.

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 ( size_t i = 0; i < size; i += 1 )
		total = total + a[i];              // select appropriate +
	return total;
}
int sa[ 5 ];
int i = sum( sa, 5 );                        // use int 0 and +

Polymorphic Type

Aggregate types may have multiple type parameters each with constraints.

forall( otype T | sumable( T ) )     // polymorphic, use trait
struct Foo {
	T * x, * y;
};
Foo( int ) foo;
int i = sum( foo.x, 5 );

Coroutines / Concurrency

Coroutines, threads, and monitors provides advanced control-flow.

Coroutine

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

// input numbers and return running total
#include <coroutine>
#include <fstream>
coroutine RunTotal {
	int input, total;                         // communication
};
void ?{}( RunTotal & rntl ) { rntl.total = 0; }
void update( RunTotal & rntl, int input ) {
	rntl.total += rntl.input;             // remember between activations
	suspend();                             // inactivate on stack
}
void main( RunTotal & rntl ) {
	for ( ;; ) {
		update( rntl, rntl.input );
	}
}
int add( RunTotal & rntl, int input ) {
	rntl.input = input;                    // pass input to coroutine
	resume( rntl );
	return rntl.total;                      // return total from coroutine
}
int main() {
	RunTotal rntl;
	for ( int i = 0; i < 10; i += 1 ) {
		sout | i | add( rntl, i ) | endl;
	}
}

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.

#include <thread>
monitor Bank {                              // shared resource
	int money;
};
void deposit( Bank & mutex bank, int deposit ) { // acquire mutual exclusion of bank on entry
	bank.money += deposit;
}
void transfer( Bank & mutex mybank, Bank & mutex yourbank, int me2you ) { // acquire mutual exlcusion of both banks on entry
	deposit( mybank, -me2you );
	deposit( yourbank, me2you );
}
monitor Buffer {
	condition full, empty;
	int front, back, count;
	int elements[20];
};
void ?{}( Buffer & buffer ) with( buffer ) { front = back = count = 0; }
int query( Buffer & buffer ) { return buffer.count; }
void insert( Buffer & mutex buffer, int elem ) with( buffer ) {
	if ( count == 20 ) wait( empty );
	elements[back] = elem;
	back = ( back + 1 ) % 20;
	count += 1;
	signal( full );
}
int remove( Buffer & mutex buffer ) with( buffer ) {
	if ( count == 0 ) wait( full );
	int elem = elements[front];
	front = ( front + 1 ) % 20;
	count -= 1;
	signal( empty );
	return elem;
}

Thread

A thread type, T, instance creates a user-level thread, which starts running in routine void main( T & ).

#include <fstream>
#include <kernel>
#include <thread>

thread Adder {
	int * row, cols, & subtotal;       // communication
};
void ?{}( Adder & adder, int row[], int cols, int & subtotal ) {
	adder.row = row;
	adder.cols = cols;
	&adder.subtotal = &subtotal;
}
void main( Adder & adder ) with( adder ) { // thread starts here
	subtotal = 0;
	for ( int c = 0; c < cols; c += 1 ) {
		subtotal += row[c];
	}
}
int main() {
	const int rows = 10, cols = 1000;
	int matrix[rows][cols], subtotals[rows], total = 0;
	processor p;                           // add kernel thread

	for ( int r = 0; r < rows; r += 1 ) {
		for ( int c = 0; c < cols; c += 1 ) {
		    matrix[r][c] = 1;
		}
	}
	Adder * adders[rows];
	for ( int r = 0; r < rows; r += 1 ) { // start threads to sum rows
		adders[r] = new( matrix[r], cols, subtotals[r] );
	}
	for ( int r = 0; r < rows; r += 1 ) { // wait for threads to finish
		delete( adders[r] );
		total += subtotals[r];         // total subtotals
	}
	sout | total | endl;
}

Libraries

Stream I/O

Polymorphic stream I/O via sin (input) and sout (output) (like C++ cin/cout) with implicit separation.

#include <fstream>                       // 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 | endl;                     // output format depends on constant/variable type
x 27 2.3                                       // implicit separation between values: 

GMP

Interface to GMP multi-precise library through type Int

// compute first 40 factorials with complete accuracy
#include <gmp>
sout | "Factorial Numbers" | endl;
Int fact = 1;                                   // multi-precise integer, 1st case
sout | 0 | fact | endl;
for ( unsigned int i = 1; i <= 40; i += 1 ) {
	fact *= i;                                 // general case
	sout | i | fact | endl;                // print multi-precise integer
}

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 '\' (backslash) for integral and floating-point types.

2 \ 8u;                                          // integral result (shifting), 256
-4 \ 3u;                                         // integral result (multiplication), -64
4 \ -3;                                           // floating-point result (multiplication), 0.015625
-4 \ -3;                                          // floating-point result (multiplication), -0.015625
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>
int main() {
	char c = 'a';
	signed char sc = 'a';
	unsigned char uc = 'a';
	sout | c | sc | uc | endl;               // prints a97 97
}

int128 Type

New basic overloadable type for 128-bit integers.

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