Cigma is an experimental programming language designed to take advantage of modern C++ within a simplified and redesigned syntax. Cigma programs can be more concise and (once learned) easier to read than equivalent C++ programs, while remaining fully interoperable with existing C++ code and toolchains.

The User Guide explains how to read, write and build programs using the Cigma programming language, refer to the Developer Guide to learn how the language is implemented and how to contribute to the project.

Introduction

Differences from C++

  • const by default

  • auto by default

  • explicit by default

  • this is a reference

  • statements can be used as expressions

  • types are read left-to-right, without spiral rules

  • grammar has no ambiguities and no vexing parse issues

  • powerful pattern matching syntax with support for current compilers (unlike the wg21 proposal)

A simpler grammar is faster to parse (important for interactive tooling) and should eventually lead to better error reporting (as the parser never needs to backtrack between multiple interpretations of the source).

How to read Cigma code

Given that Cigma has a different syntax from C++ (and other C-inspired languages), code will look unfamiliar at first. Refer to the following rules to get started:

  • $ means declare

  • . means member

  • : means type

  • :: means concept

  • / means namespace

  • % means module

  • @ means attribute

  • ! means mutable (in types)

  • ' means pointer (in types) or dereference (in expressions)

  • ^ means rvalue reference (in types) or std::move (in unary expressions)

  • -> means return

  • [ ] means compile-time

Compile-time syntax in particular is designed to compose well with the rest of the language, taking the place of the constexpr, constinit, consteval and template keywords from C++.

Getting started

Installation

From sources

To use the language on a platform where prebuilt binaries are not yet available, follow the compilation instructions in the Developer Guide.

Language

This section describes the Cigma programming language. It is liberally modeled after the language section of the cppreference wiki because most language constructs map directly to C++.

Basic elements

See also: cppreference.com

Identifiers

See also: cppreference.com

An identifier is a sequence of digits, underscores, lowercase and uppercase Latin letters. A valid identifier must begin with a non-digit character (Latin letter or underscore).

An identifier must always be written including its prefix, if any. A prefix unambiguously specifies what language element an identifier refers to:


 id   (1)
.id   (2)
:id   (3)
::id  (4)
/id   (5)
@id   (6)
%id   (7)
%%id  (8)
>id   (9)
1 Namespace member (or local variable)
2 Class member
3 Type identifier
4 Concept identifier
5 Namespace identifier
6 Label or attribute identifier
7 Module identifier
8 Module partition identifier
9 Lifetime identifier
_ (a single underscore) can form valid identifiers and is reserved to identify unused entities that are not referenced elsewhere in a program.

A dollar sign is always used to declare new identifiers:


$/mynamespace {
  $ myconst:int = 10;
  $ myfunc:(a:int, _:bool) -> :{ $b: = 10, -> myconst + a * b };
  $:myclass{ $.x, .y:int };
};

Comments

See also: cppreference.com

Inserting a # in Cigma source code starts a comment. Comments extend until the end of the physical line of code. Any text included in a comment is ignored by the Cigma compiler. Comments are often used to include additional human-readable information inside source code.


# this is a comment
# spanning two lines

$ main:() -> :!int
{  (1)
  #-> 1;
   (2)
  -> 0;
};
1 This line will not be executed
2 This line will be executed

Expressions

See also: cppreference.com

Compile-time expressions

See also: cppreference.com


$ n:int = [/std:numeric_limits[:int] max()];

$[:T]:Is {
  $[:Arg].extract:(arg:^Arg) -> :!Arg {
    [assert([:T == :~volatile~&Arg])];  (1)
    -> ^[:Arg]arg;
  };
};

$[:T] is:Is[:T]<[];  (2)
1 Compile-time assertion
2 Variable initialized at compile-time (implicitly inline)

/std cout << [:int == :/std:int32_t] << "\n"';  (1)
/std cout << [:int != :/std:int64_t] << "\n"';  (2)
1 Usually true if int is 32-bit
2 Possibly false if ILP64 data model is used

[assert([:~~int == :!int])];  (1)
[assert([:~~&int == :!int])];  (2)
[assert([:&~&int == :&int])];  (3)
[assert([:~& &int == :int])];  (4)
[assert([:~&^int == :!int])];  (5)
[assert([:~&!int == :!int])];  (6)
1 ! is required because :T implies immutability ( const T in C++)
2 ~~ removes both the reference and the (implicit) const qualifier
3 Qualifiers are applied from left to right
4 ~& is applied to &int
5 Temporaries are references, and they are also mutable
6 ~& has no effect on non-references

Literals

See also: cppreference.com

String literals

See also: cppreference.com


$str1: = "single'quote";
$str2: = "double\"quote";

$char1: = "c"';
$char2: = "'"';
$char3: = "\""';
$char4: = "\\"';
$char5: = u"a";

$str_L: = L"wide";  (1)
$str_u8: = u8"utf8";  (2)
$str_u: = u"utf16";  (3)
$str_U: = U"utf32";  (4)
1 Wide string literal of type :.(N) wchar_t
2 UTF-8 literal of type :.(N) char8_t
3 UTF-16 literal of type :.(N) char16_t
4 UTF-32 literal of type :.(N) char32_t

$raw1: = R"(double\"quote)";
$raw2: = R"break(double\")quote)break";
$raw3: = R"foo(double\"quote
$mbed: = R"break(double\")quote)foo";
User-defined literals

See also: cppreference.com


$``_deg:[deg:long double] -> :long double  (1)
{
  -> deg * 3.14159265358979323846264L / 180;
};

$:mytype { $.m:unsigned long long int };
$``_mytype:[n:unsigned long long int] -> :mytype  (2)
{
  -> :mytype<{n};
};

$``_print:(str:'char) ->  (3)
{
  /std cout << str;
};
1 Used as conversion
2 Used with custom type
3 Used for side-effects

$x:double = 90.0_deg;
/std cout << /std fixed << x << "\n"';
$y:mytype = 123_mytype;
/std cout << y.m << "\n"';
0x123ABC_print;

Types

See also: cppreference.com

Pointers

See also: cppreference.com

Pointer-to-object

$:C { $.x, .y:!int };
$ c:!C;

$ px:'!int = &c.x;
$ pxe:'!int = px + 1;
$ py:'int = &c.y;

pxe' = 1;

$n:!int;
$np:'!int = &n;
$npp:!''!int = &np;

$a:.(2)!int;
$ap:'.(2)!int = &a;

$:S { $.n:!int };
$s:!S<{1};
$sp:'!int = &s.n;

$n:!int;
$p:'!int = &n;
$r:&!int = p';
r = 7;
/std cout << p';

$a:.(2)!int;
$p1:'!int = a;

$b:.(6).(3).(8)!int;
$p2:'.(3).(8)!int = b;

$:Base {};
$:Derived { +$:Base };

$d:Derived;
$b:'Base = &d;
Pointer-to-function

$f:(_:int) -> {};
$p1:'(_:int) -> = &f;
$p2:'(_:int) -> = f;  (1)
1 Using the id by itself implicitly takes its address

$ f:(n:int) -> :!int {
  /std cout << n << /std endl;
  -> n * n;
};

$p:'(_:int) -> :!int = f;
$x:int = p(7);

$f:() -> :int { -> 0 };
$p:'() -> :int = f;   (1)
$r:&() -> :int = p';  (2)
r();   (3)
p'();  (4)
p();   (5)
1 Pointer p is pointing to f
2 The lvalue that identifies f is bound to a reference
3 Function f invoked through lvalue reference
4 Function f invoked through the function lvalue
5 Function f invoked directly through the pointer

$[:T] f:(n:T) -> :T { -> n };
$ f:(n:double) -> :double { -> n };

$p:'(_:int) -> :int = f;  (1)
1 Instantiates and selects f[:int]
Pointer-to-member-data

$:C { $.m:!int };

$p:!C'.(!int) = :C&.m;
$c:!C<{7};
/std cout << c.'(p) << /std endl;

$cp:'!C = &c;
cp'.m = 10;
/std cout << cp'.'(p) << /std endl;

$:Base { $.m:!int };
$:Derived { +$:Base };

$bp:!Base'.(!int) = :Base&.m;
$dp:!Derived'.(!int) = bp;
$d:!Derived;

d.m = 1;
/std cout << d.'(dp) << " " << d.'(bp) << /std endl;

$:Base { $.m:!int };
$:Derived { +$:Base, $.m:!int };

$dp:!Derived'.(!int) = :Derived&.m;
$bp:!Base'.(!int) = [:!Base'.(!int) = dp];

$d:!Derived;
d.m = 7;
/std cout << d.'(bp) << /std endl;  (1)

$b:Base;
/std cout << b.'(bp) << /std endl;  (2)
1 OK: prints 7
2 Undefined behavior

$:A {
  $.m:!int;
  $.p:A'.(!int);  (1)
};
$p1:!A'.(A'.(!int)) = :A&.p;  (2)

$a:A<{1, :A&.m};
/std cout << a.'(a.'(p1)) << /std endl;  (3)

$p2:!'A'.(int) = &a.p;  (4)
/std cout << a.'(p2') << /std endl;  (5)
1 Immutable pointer to mutable member
2 Mutable pointer to data member which is a immutable pointer to mutable member
3 Prints 1
4 Mutable pointer to immutable pointer to member
5 Prints 1
Pointer-to-member-function

$:C {
  $.f:(n:int) -> {
    /std cout << n << /std endl;
  };
};
$p:C'.((_:int) ->) = :C&.f;

$c:C;
(c.'(p))(1);  (1)

$cp:'C = &c;
(cp'.'(p))(2);  (2)

:C&.f(cp, 2);  (3)
1 Prints 1
2 Prints 2
3 Prints 2

$:Base {
  $.f:(n:int) -> {
    /std cout << n << /std endl;
  };
};
$:Derived { +$:Base };

$bp:Base'.((_:int) ->) = :Base&.f;
$dp:Base'.((_:int) ->) = bp;
$d:Derived;
(d.'(dp))(1);
(d.'(bp))(2);

$:Base {};
$:Derived {
  +$:Base;
  $.f:(n:int) -> {
    /std cout << n << /std endl;
  };
};

$dp:Derived'.((_:int) ->) = :Derived&.f;
$bp:Base'.((_:int) ->) = [:Base'.((_:int) ->) = dp];

$d:Derived;
(d.'(dp))(1);

$b:Base;
(b.'(bp))(2);  (1)
1 Undefined behavior

Typeclasses

See also: wikibooks.com

See also: foonathan.net


$:Car { $.accelerate:() -> { /std cout << "wroom\n" } };  (1)

$:RoadRunner { $.accelerate:() -> { /std cout << "meep meep\n" } };  (2)

$:Plane {
  $.rate:size_t;
  $.accelerate:() -> { /std cout << "wh" << :/std:string<(.rate, "o"') << "sh\n" }
};

$/lib
{  (3)
  $:Motorcycle { $.accelerate:() -> { /std cout << "brrrrrr\n" } };
};

$:Vehicle? { $.accelerate:() -> };  (4)

$ makeVehicle:(name:&/std:string) -> :!Vehicle
{
  switch name {
    "car" { -> :Car<() };
    "plane" { -> :Plane<{4} };
    "roadrunner" { -> :RoadRunner<() };
    _ { exit(1); };  (5)
  };
};
1 No inheritance
2 Not even properly a "vehicle", but implements the required interface
3 Third-party types from external libraries can be used as-is if they satisfy the requirements
4 The ? suffix in the type name denotes a typeclass
5 A typeclass cannot be instantiated without a concrete type

$vehicles:!/std:vector[:!Vehicle];
vehicles.push_back(makeVehicle("car"));
vehicles.push_back(makeVehicle("plane"));
vehicles.push_back(makeVehicle("roadrunner"));
vehicles.push_back(:!/lib:Motorcycle<());

for $..vehicle:& = vehicles {
  vehicle.accelerate();
};

Retrieve the type of an expression or entity

See also: cppreference.com


$:A { $.x:!double };
$ pa:!'A = nullptr;
$ y:![(pa'.x)];  (1)
$ z:[((pa'.x))] = y;  (2)

$[:T, :U] add:(t:T, u:U) -> :[(t + u)]  (3)
{
  -> t + u;
};

$ main:() -> :!int
{
  $i:int = 33;
  $j:[(i)] = i * 2;

  $f: = _:(a, b:int) -> :int { -> a * b };

  $g:[(f)] = f;  (4)
  $fr: = f(2, 2);
  $gr: = g(3, 3);

  -> 0;
};
1 t has type :double (also null pointer is not dereferenced in unevaluated context).
2 z has type :&double (extra parentheses turn it into lvalue expression).
3 Infer return type from expression (not required since C++14).
4 The type of a lambda function is unique and unnamed.

Declarations

Visibility rules

Namespaces
$ (public visibility)

The namespace can contain declarations with any visibility specifier.

$$ (protected visibility)

The namespace can only contain protected and private declarations.

$$$ (private visibility)

The namespace can only contain private declarations.

Types
$ (public visibility)

Type is fully visible to external modules, its definition appears in the generated header file.

$$ (protected visibility)

Type can be used by external modules in name only, only a forward declaration appears in the generated header file. The full definition appears in the generated implementation file.

$$$ (private visibility)

Type can only be used within the module. Its definition only appears in the generated implementation file.

Members

This includes namespace members. Members can be either variables or functions.

$ (public visibility)

Can be used by external modules. Its definition appears in the generated header file.

$$ (protected visibility)

Its definition only appears in the generated implementation file but the member has external linkage.

$$$ (private visibility)

Can only be used within the module. The member is defined only in the generated implementation file and has internal linkage.

Locals

Visibility rules are ignored for local variables and only $ (public visibility) should be used.

Storage duration

See also: cppreference.com

Thread storage duration

$$ counter:!int @{thread} = 1;  (1)
$$ cout_mutex:!/std:mutex;

$$ increase_counter:(thread_name:&/std:string) ->
{
  ++counter;  (2)
  $lock:/std:lock_guard[:!/std:mutex]<(cout_mutex);
  /std cout << "Counter for " << thread_name << ": " << counter << "\n"';
};

$$ main:() -> :!int
{
  $a<(increase_counter, "a"), b<(increase_counter, "b"):!/std:thread;
  {
    $lock:/std:lock_guard[:!/std:mutex]<(cout_mutex);
    /std cout << "Counter for main: " << counter << "\n"';
  };
  a.join();
  b.join();
};
1 Declare counter with thread storage
2 Thread-local variable can be modified without locks

Enumerations and enumerators

See also: cppreference.com


$:Color = |:{ red, green, blue };
$r:Color = red;

$:Foo = |:{
  a, b, c: = 10;
  d, e: = 1;
  f, g: = f + c;
};

$:smallenum = |:int16_t{ a, b, c };

$|:{ a, b, c: = 0, d: = a + 2 };

$:Color |:{ red, green: = 20, blue };

$:Handle |:uint32_t{ Invalid: = 0 };

Type aliases

See also: cppreference.com


$[:T, :A]:vector {};
$[:T]:Alloc {};
$[:T]:Vec = :vector[:T, :Alloc[:T]];
$ v:Vec[:int];  (1)
1 :Vec[:int] is the same as :vector[:int, :Alloc[:int]]

$[:_..]:void_tpl = :void;
$[:T] f:() -> :void_tpl[:'T:foo] {?};

f[:int]();  (1)
1 Error: int does not have a nested type foo

Namespace aliases

See also: cppreference.com


$/foo {
  $/bar/baz {
    $ qux:int = 42;
  };
};

$/fb = /foo/bar;

$/fbz = /fb/baz;

/std cout << /fbz qux << /std endl;

Operator overloads

See also: cppreference.com

Assignment operators
Copy assignment

$.`=`:(other:&T)! -> :&!T {
  if &this != &other {
    # ...prevent self-assignment...
  };
  -> this;
};
Move assignment

$.`=`:(other:^T)! -> :&!T {
  -> this;
};
Stream extraction and insertion operators

$[:T] `>>`:(is:&/std:istream, obj:&!T) -> :&/std:istream {
  ...read object from stream...
  -> is;
};

$[:T] `<<`:(os:&!/std:ostream, obj:&T) -> :&!/std:ostream {
  ...write object to stream...
  -> os;
};
Function call operators

$:Sum {
  $.sum:!int = 0;
  $.(n:int)! -> { .sum += n };
};
$n: = <{1, 2, 3, 4};
$s:Sum = /std for_each(n.begin(), n.end(), :Sum<());
Array subscript operators

$[:T]:subscriptable {
  $.(idx:int) :&T { -> .at(idx) };
  $.(idx:int)! :&!T { -> .at(idx) };
};
Custom operators

$:Vec {
  $.x, .y, .z:!double;
  $<:(X, Y, Z:double = 0) <{
    .x: = X, .y: = Y, .z: = Z;
  };
  $.`+`:(v:&Vec) -> :Vec {  (1)
    -> :Vec<(.x + v.x, .y + v.y, .z + v.z);
  };
  $.`-`:(v:&Vec) -> :Vec {  (2)
    -> :Vec<(.x - v.x, .y - v.y, .z - v.z);
  };
  $.`*`:(d:double) -> :Vec {  (3)
    -> :Vec<(.x * d, .y * d, .z * d);
  };
  $.`1`:()! -> :&!Vec {  (4)
    -> this`=`this`*`(1 / sqrt(.x * .x + .y * .y + .z * .z));
  };
  $.`.*`:(v:&Vec) -> :double {  (5)
    -> .x * v.x + .y * v.y + .z * v.z;
  };
  $.`%`:(b:&Vec) -> :Vec {  (6)
    -> :Vec<(
      .y * b.z - .z * b.y;
      .z * b.x - .x * b.z;
      .x * b.y - .y * b.x;
    );
  };
};
1 Vector sum (standard operator)
2 Vector difference (standard operator)
3 Vector scaling (standard operator)
4 Vector normalization (custom operator)
5 Dot product (custom operator)
6 Cross product (standard operator)

Standard and custom operators can then be invoked as follows:


$.intersect:(r:&Ray) -> :double {
  $op: = .p`-`r.o;
  $b: = op`.*`r.d;
  $det: = b * b - op`.*`op + rad * rad;
  if det < 0 { -> 0 };
  $srd: = sqrt(det);
  $eps: = 1e-4;
  if $t: = b - srd, t > eps { -> t };
  if $t: = b + srd, t > eps { -> t };
  -> 0;
};

Structured bindings

See also: cppreference.com


$ a:.(2) int <{1, 2};
${x, y}: = a;
${xr, yr}:& = a;

$ myset:!/std:set[:!/std:string];
if ${iter, success}: = myset.insert("Hello"); success {
  /std cout << "insert is successful. The value is " << /std quoted(iter') << "\n"';
} else {
  /std cout << "The value " << /std quoted(iter') << " already exists in the set\n";
};

$ mymap:!/std:map[:/std:string, :float];
for $..{key, value}:^ = mymap {
  ...
};

$ x:float<{};
$ y:!char<{};
$ z:int<{};
$ tpl:.[:&float, :^char, :int]<{x, ^y, z};  (1)
${a, b, c}:& = tpl;
1 :.[…​] is shorthand for :/std:tuple

Initialization

Initialization in Cigma can be expressed in two ways, depending on the author’s intent:

  1. The first form uses <(…​) syntax and performs direct initialization.

  2. The second form uses <{…​} syntax and performs aggregate initialization.

Functions

See also: cppreference.com

Default arguments

See also: cppreference.com


$ func:(x<{3}, y<{4}:int) -> {};

func();
func(<(), 8);
func(<{}, 9);

Lambdas

See also: cppreference.com


$:S2 {
  $.f:(i:int) -> {
    _:() \(&) {};           (1)
    _:() \(&, i) {};        (2)
    _:() \(&, i&) {};       (3)
    _:() \(&, this) {};     (4)
    _:() \(&, this, i) {};  (5)
  };
};
1 OK: by-reference capture default
2 OK: by-reference capture, except i is captured by copy
3 Error: by-reference capture when by-reference is the default
4 OK: equivalent to \(&)
5 OK: equivalent to \(&, i)

$x:!int = 4;
$y: = (_:() -> :int \(r& = x, x = x + 1) {
  r += 2, -> x * x;
})();  (1)
1 Updates x to 6 and initializes y to 25

$ make_function:(x:&int) -> :{
  -> _:() \(&) { /std cout << x << /std endl };
}

$i:!int = 3;
$f: = make_function(i);
i = 5;
f();  (1)
1 OK: prints 5

$ use:(x, y:int) -> {
  ...
}

$:S {
  $.x:!int = 0;
  $.f:()! -> {
    $i:!int = 0;
    $l1: = _:() \(i, .x) { use(i, .x) };  (1)
    $l2: = _:() \(i, x = .x) { use(i, x) };  (2)
    i = 1, .x = 1, l2();  (3)
    $l3: = _:() \(i, x& = .x) { use(i, x) };  (4)
    i = 2, .x = 2, l3();  (5)
  };
};
1 Error: x is not a variable
2 OK: copy capture
3 Calls use(0, 0)
4 OK: reference capture
5 Calls use(1, 2)

Compile-time functions


$ gcd:[a, b:int] -> :int {
  -> (if b == 0 {a} else {gcd(b, a % b)});
};

$ i:int = [gcd(11, 121)];  (1)

$ a: = 11;
$ b: = 121;
$ j:int = gcd(a, b);  (2)
1 Evaluated at compiletime
2 Evaluated at runtime

Statements

See also: cppreference.com

If statements

See also: cppreference.com


$i:int = 2;
if i > 2 {
  /std cout << i << " is greater than 2" << /std endl;
} else {
  /std cout << i << " is not greater than 2" << /std endl;
};

$ m:!/std:map[:int, :!/std:string];
$ mx:!/std:mutex;
$ shared_flag:!bool;
$ demo:() -> :int {
  if $it: = .m.find(10), (it != .m.end()) {
    -> it'.second.size();
  };
  if $buf:.(10)!char, /std fgets(buf, 10, stdin) {
    .m.(0) += buf;
  };
  if $lock:/std:lock_guard<(.mx), shared_flag {
    shared_flag = false;
  };
  if $s:!int, $count:int = scanf("%d", &s) {
    if $keywords: = <{"if", "for", "while"};
     /std any_of(keywords.begin(), keywords.end(),
      _:(kw:'char) \(s&) { -> s == strlen(kw) }) {
      /std cerr << "Token must not be a keyword" << /std endl;
    };
  };
};

$[:T] get_value:(t:T) -> :{
  if [/std is_pointer_v[:T]] {
    -> t';  (1)
  } else {
    -> t;  (2)
  };
};
1 deduces return type to :int for :T = :'int
2 deduces return type to :int for :T = :int

For statements

See also: cppreference.com

Typical loop with a single statement as the body:


for $i:! = 0, i < 10, ++i {
  /std cout << i << " "';
};
/std cout << "\n"';

$ v:/std:vector[:!int]<{3, 1, 4, 5, 9};
for $iter:! = v.begin(); iter != v.end(); ++iter {
  /std cout << iter' << " "';
};
/std cout << "\n"';

$ v:/std:vector[:!int]<{1, 2, 3, 4, 5};
for $..i:&int = v {  (1)
  /std cout << i << " "';
};
/std cout << "\n"';
for $..i: = v {  (2)
  /std cout << i << " "';
};
/std cout << "\n"';
for $..i:^ = v {  (3)
  /std cout << i << " "';
};
/std cout << "\n"';
$ cv:& = v;
for $..i:^ = cv {  (4)
  /std cout << i << " "';
};
/std cout << "\n"';
for $..n:int<{0, 1, 2, 3, 4, 5} {  (5)
  /std cout << n << " "';
};
/std cout << "\n"';
1 Access by const reference
2 Access by value, the type of i is :!int
3 Access by forwarding reference, the type of i is :&!int
4 Access by forwarded reference, the type of i is :&int
5 May use aggregate initializer directly

Switch statements


$n: = 5;
switch n {
  0 { /std cout << "zero" };
  1...10 { /std cout << "between 1 and 10" };
  | { /std cout << "other" };
};

$n: = 0;
switch n {
  | 1 | 2 | 3 {
    /std cout << "small positive";
  } @{fallthrough};
  | {
    /std cout << "don't care";
  };
};

$s: = "baz"s;
switch s {
  "foo" { /std cout << "got foo" };
  "bar" { /std cout << "got bar" };
      | { /std cout << "don't care" };
};

$p:.[:int, :int] <{1, 2};
switch p {
  ${0,   0} { /std cout << "on origin" };
  ${0,   _} { /std cout << "on y-axis" };
  ${_,   0} { /std cout << "on x-axis" };
  ${$x, $y} { /std cout << x << ", " << y };
};

$v:|[:int, :float] = 84;
switch v {
  .[:int]$i { /std cout << "got int:" << i };
  .[:float]$f { /std cout << "got float: " << f };
};

$a:.(:?) = 48;
switch a {
  .(:int)$i { /std cout << "got int:" << i };
  .(:float)$f { /std cout << "got float: " << f };
};

$v:|[:int, :int]<(.[0] = 5);
switch v {
  .[0]$i0 { /std cout << "got first int:" << i0 };
  .[1]$i1 { /std cout << "got second int: " << i1 };
};

switch t {
  [:int] { /std cout << "got int:" << t };
  [:float] { /std cout << "got float: " << t };
};

$:Shape { $>:() @{default} };
$:Circle { +$:Shape, $.radius:double };
$:Rectangle { +$:Shape, $.width, .height:double };

$ get_area:(shape:&Shape) -> :double {
  -> (switch shape {
    (:Circle)${$r} { 3.14 * r * r };
    (:Rectangle)${$w, $h} { w * h };
  });
};

$:Player{
  $.name:!/std:string, $.hitpoints, .coins:!int = 0;
  $.ni:(_:int) -> :{ -> .name };
};
$p:!Player;
switch (p) {
  .{.hitpoints ~ 1} { /std cout << "Almost destroyed" };
  .{.hitpoints ~ 0...9, .coins ~ 10} { /std cout << "Need hints" };
  .{.coins ~ 10} { /std cout << "Get more hitponts" };
  .{.hitpoints ~ 10}$e { /std cout << "Get more ammo to " << e.name };
  .{.ni(0) ~ $n} && n == "The Bruce Dickinson" {
    /std cout << "More cowbell";
  };
};

$:Color = |:{Red, Black};
$[:T]:Node {
  $.color:!Color;
  $.lhs:!/std:shared_ptr[:!Node];
  $.value:!T;
  $.rhs:!/std:shared_ptr[:!Node];
  $.balance:()! -> {
    this = (this ~ {
      ${Black, ?'${Red, ?'${Red, $a, $x, $b}, $y, $c}, $z, $d} {
        :Node<{Red, /std make_shared[:!Node](Black, a, x, b),
                 y, /std make_shared[:!Node](Black, c, z, d)};
      };
      ${Black, ?'${Red, $a, $x, ?'${Red, $b, $y, $c}}, $z, $d} {
        :Node<{Red, /std make_shared[:!Node](Black, a, x, b),
                 y, /std make_shared[:!Node](Black, c, z, d)};
      };
      ${Black, $a, $x, ?'${Red, ?'${Red, $b, $y, $c}, $z, $d}} {
        :Node<{Red, /std make_shared[:!Node](Black, a, x, b),
                 y, /std make_shared[:!Node](Black, c, z, d)};
      };
      ${Black, $a, $x, ?'${Red, $b, $y, ?'${Red, $c, $z, $d}}} {
        :Node<{Red, /std make_shared[:!Node](Black, a, x, b),
                 y, /std make_shared[:!Node](Black, c, z, d)};
      };
      | { this };
    });
  };
};

Classes

See also: cppreference.com

Virtual functions

See also: cppreference.com


$:Base {
  $.f:()? -> {
    /std cout << "base" << /std endl;
  };
};

$:Derived {
  +$:Base;
  $.f:()? -> @{override} {
    /std cout << "derived" << /std endl;
  };
};

$b:Base;
$d:Derived;

$br:&Base = b;
$dr:&Base = d;

# Virtual calls through reference
br.f();  (1)
dr.f();  (2)

$bp:'Base = &b;
$dp:'Base = &d;

# Virtual calls through pointer
bp'.f();  (3)
dp'.f();  (4)

# Non-virtual calls with explicit base
br.:Base.f();  (5)
dp'.:Base.f();  (6)
1 Prints "base"
2 Prints "derived"
3 Prints "base"
4 Prints "derived"
5 Prints "base"
6 Prints "base"

Final

See also: cppreference.com


$:Base {
  $<:() @{default};
  $.foo:()? -> {};
};
$:A {
  +$:Base;
  $.foo:()? -> @{final} {};  (1)
  $.bar:()? -> @{final} {};  (2)
};
$:B @{final} {
  +$:A;
  $.foo:()? -> @{override} {};  (3)
};
$:C {
  +$:B;  (4)
};
1 Final override of :Base.foo
2 Error: bar is not an override and cannot be final
3 Error: cannot override a final method
4 Error: cannot extend a final class

Bit fields

See also: cppreference.com


$:S @{packed} {
  $.b1:!uint8_t @{bits[3]};
  $._ :!uint8_t @{bits[0]};  (1)
  $.b2:!uint8_t @{bits[6]};
  $.b3:!uint8_t @{bits[2]};
};
1 Start a new byte

/std cout << sizeof[:S] << "\n"';  (1)
1 Usually prints 2

Templates

See also: cppreference.com

Template parameters

See also: cppreference.com

Type template parameters

$[:T]:My_vector {  (1)
  ...
};
$[:T = :bool]:My_op_functor {  (2)
  ...
};
$[:Ts..]:My_tuple {  (3)
  ...
};
1 Type template parameter without a default
2 Type template parameter with a default
3 Type template parameter pack
Template template parameters

$[:T]:my_array {};
$[:K, :V, [:_]:C = :!my_array]:Map {  (1)
  $.key:C[:!K];
  $.value:C[:!V];
};
1 Two type template parameters and one template template parameter

$[:T]:A { $.x:!int };        (1)
$[:T]:A[:'T] { $.x:!long int };  (2)

$[[:_]:V]:C {  (3)
  $.y:!V[:int];   (4)
  $.z:!V[:'int];  (5)
};

$ c:!C[:A[]];  (6)
1 Primary template
2 Partial specialization
3 Class template with a template template parameter V
4 Uses the primary template
5 Uses the partial specialization
6 c.y.x has type :int, c.z.x has type :long

$[:T]:eval {?};  (1)
$[[:_, :_..]:TT, :T1, :Rest..]:eval[:TT[:T1, :Rest..]] {};  (2)
$[:T1]:A {?};
$[:T1, :T2]:B {?};
$[N:int]:C {?};
$[:T1, N:int]:D {?};
$[:T1, :T2, N:int = 17]:E {?};

$ eA:eval[:A[:int]];  (3)
$ eB:eval[:B[:int, :float]];  (4)
$ eC:eval[:C[17]];  (5)
$ eD:eval[:D[:int, 17]];  (6)
1 Primary template
2 Partial specialization of eval
3 OK: matches partial specialization of eval
4 OK: matches partial specialization of eval
5 Error: C does not match TT in partial specialization because TT 's first parameter is a type template parameter, while 17 does not name a type
6 Error: D does not match TT in partial specialization because TT 's second parameter is a type parameter pack, while 17 does not name a type

$[:T]:A {};
$[:T, :U = :T]:B {};
$[:Types..]:C {};
$[[:_]:P]:X {};

$ xa:X[:A[]];  (1)
$ xb:X[:B[]];  (2)
$ xc:X[:C[]];  (3)

$[[:_..]:Q]:Y {};
$ ya:Y[:A[]];  (4)
$ yb:Y[:B[]];  (5)
$ yc:Y[:C[]];  (6)
1 Ok
2 Error until C++17
3 Error until C++17
4 Ok
5 Ok
6 Ok

Member templates

See also: cppreference.com


$[:T]:S {
  $[:U].foo:() -> {};
};
$[:T] bar:() -> {
  $s:S[:T];
  s.foo[:T]();
};

Variable templates


$[:T] pi:T = [3.1415926535897932385L];

$[:T] circular_area:[r:T] -> :T {
  -> pi[:T] * r * r;
};

$ a: = [circular_area[:float](10)];

$[Value:int] fib: = fib[Value-1] + fib[Value-2];
$[] fib[0]: = 0;
$[] fib[1]: = 1;

$ f: = fib[3];

$[:T, :U] is_same:bool = [false];
$[:T] is_same[:T, :T]:bool = [true];

$ s: = is_same[:int, :float];

$[:T] add: = _:[lhs, rhs:&T] { -> lhs + rhs };

$ l: = add[:int](3.1, 4234.2);

Parameter packs

See also: cppreference.com


$[:Types..]:Tuple {};
$:t0 = :Tuple[];
$:t1 = :Tuple[:int];
$:t2 = :Tuple[:int, :float];
$:error = :Tuple[0];  (1)
1 Would not compile

$[:Us..] f:(pargs:Us..) -> {};
$[:Ts..] g:(args:Ts..) -> { f(&args..) };

g(1, 0.2, "a");

$[:Us..] f:(args:Us..) -> {};
$[:Ss..] h:(args:Ss..) -> {};
$[:Ts..] g:(args:Ts..) -> {
  $ n: = sizeof..[:Ts];
  f(&args..);  (1)
  f(n, ++args..);  (2)
  f(++args.., n);  (3)
  f([:'Ts = &args]..);  (4)
  f(h(args..) + args..);  (5)
};
1 Expands to f(&E1, &E2, &E3)
2 Expands to f(n, E1, E2, ++E3)
3 Expands to f(E1, E2, ++E3, n)
4 Expands to f([:'E1 = &X1], [:'E2 = &X2], [:'E3 = &X3])
5 Expands to f(h(E1, E2, E3) + E1, f(E1, E2, E3) + E2, f(E1, E2, E3) + E3)

Utilities

See also: cppreference.com

Reference wrapper

See also: cppreference.com


$l:!/std:list[:!int]<(10);

/std iota(l.begin(), l.end(), -4);
$v:!/std:vector[:!&int]<(l.begin(), l.end());

# Can't use shuffle on a list (requires random access), but can use it on a vector
/std shuffle(v.begin(), v.end(), :/std:mt19937<(:/std:random_device<()()));

/std cout << "Contents of the list: ";
for $..n: = l {
  /std cout << n << " "';  (1)
};

/std cout << "\nContents of the list, as seen through a shuffled vector: ";
for $..i: = v {
  /std cout << i << " "';  (2)
};

/std cout << "\nDoubling the values in the initial list...";
for $..i:&! = l {
  i *= 2;
};

/std cout << "\nContents of the list, as seen through a shuffled vector: ";
for $..i: = v {
  /std cout << i << " "';  (3)
};
1 Prints: -4 -3 -2 -1 0 1 2 3 4 5
2 Prints for example: 3 -1 1 4 2 -4 0 -2 -3 5
3 Prints for example: 6 -2 2 8 4 -8 0 -4 -6 10

Bind

See also: cppreference.com

See also: cppreference.com

See also: cppreference.com


$ goodbye:(s:&/std:string) -> { /std cout << "Goodbye " << s << "\n"'; };

$:Object
{
  $.hello:(s:&/std:string) -> { /std cout << "Hello " << s << "\n"'; };
};

$:ExampleFunction = :!?(_:&/std:string) ->;
$instance:Object;
$str:/std:string = "World";
$f:ExampleFunction = :Object&.hello\(&instance, \1);
f(str);  (1)

$g:ExampleFunction = &goodbye\(\1);
g(str);  (2)
1 Equivalent to instance.hello(str)
2 Equivalent to goodbye(str)

$ f:(n1:&!int, n2:&!int, n3:&int) -> {
  /std cout << "In function: " << n1 << " "' << n2 << " "' << n3 << "\n"';
  ++n1;  (1)
  ++n2;  (2)
  ++n3;  (3)
};
1 Captured by value (below)
2 Captured by reference (below)
3 Error: immutable variable

$n1<{1}, n2<{2}, n3<{3}:!int;
$bound_f:?() -> = f\(n1, n2&!, n3&);
n1 = 10;  (1)
n2 = 11;  (2)
n2 = 12;  (3)
/std cout << "Before function: " << n1 << " " << n2 << " " << n3 << "\n"';  (4)
bound_f();  (5)
/std cout << "After function: " << n1 << " " << n2 << " " << n3 << "\n"';  (6)
1 bound_f still stores initially captured value
2 bound_f only stores a reference, f sees new value
3 Same as n2, but the reference is immutable
4 Prints: 10 11 12
5 Prints: 1 11 12
6 Prints: 10 12 12

$ f:(n1, n2, n3:int, n4:&int, n5:int) -> {
  /std cout << "In function: "
   << n1 << " "' << n2 << " "' << n3 << " "' << n4 << " "' << n5 << "\n"';
};

$ g:(n1:int) -> :int { -> n1 };

$:Foo {
  $.print_sum:(n1, n2:int) -> { /std cout << n1 + n2 << "\n"' };
  $.data:!int = 10;
};

$n:!int = 7;
$f1: = f\(\2, 42, \1, n&, n);
n = 10;
f1(1, 2, 1001);  (1)

$f2: = f\(3, g\(\3), \3, 4, 5);  (2)
f2(10, 11, 12);  (3)

$foo:Foo;
$f3: = :Foo&.print_sum\(&foo, 95, \1);  (4)
f3(5);

$f4: = :Foo&.data\(\1);  (5)
/std cout << f4(foo) << "\n"';

/std cout << f4(/std make_shared[:Foo](foo)) << "\n"'  (6)
          << f4(/std make_unique[:Foo](foo)) << "\n"';

$e:/std:default_random_engine;
$d:/std:uniform_int_distribution[] <(0, 10);
$rnd:! = d\(e);  (7)
for $n:! = 0, n < 10, ++n {
  /std cout << rnd() << " "';
};
/std cout << "\n"';
1 Calls f(2, 42, 1, n, 7) (1001 is unused)
2 Nested binds use same placeholders indices
3 Calls f(12, g(12), 12, 4, 5)
4 Bind to pointer to member function
5 Bind to pointer to data member
6 Bound object also accepts smart pointers
7 Store a copy or e in rng

Optional

See also: cppreference.com


$ create:(b:bool) -> :?/std:string {
  if b {
    -> "Godzilla";
  };
  -> <();  (1)
};
$ create2:(b:bool) -> :?/std:string {  (2)
  if b { -> "Godzilla" } else { -> /std nullopt };
};
$ create_ref:(b:bool) -> :?!&!/std:string {  (3)
  $value:!/std:string @{once} = "Godzilla";
  if b { -> value } else { -> /std nullopt };
};
1 Default constructor creates an empty optional
2 Alternatively, /std nullopt also deduces an empty optional
3 Returns immutable optional ( ? ) reference wrapper ( !& ) to mutable ( ! ) string

/std cout << "create(false) returned " << create(false).value_or("empty") << "\n"';  (1)
if $str: = create2(true) {
  /std cout << "create2(true) returned " << str' << "\n"';  (2)
};
if $str: = create_ref(true) {
  /std cout << "create_ref(true) returned " << str'.get() << "\n"';  (3)
  str.reset();  (4)
  str'.get() = "Mothra";  (5)
  /std cout << "modifying it changed it to " << str'.get() << "\n"';  (6)
};
1 Prints empty
2 Prints Godzilla
3 Prints Godzilla
4 Not allowed, str is immutable
5 Allowed, changes the referenced string
6 Prints Mothra

Function

See also: cppreference.com


$:Foo {
  $<:(n:int) <{.num := n};
  $.print_add:(i:int) -> { /std cout << .num + i << "\n"' };
  $.num:int;
};

$ print_num:(i:int) -> { /std cout << i << "\n"' };

$:PrintNum {
  $.(i:int) -> { /std cout << i << "\n"' };
};

$f_display:?(_:int) -> = print_num;  (1)
f_display(-9);

$f_display_42:?() -> = _ { print_num(42) };  (2)
f_display_42();

$f_add_display:?(_:&Foo, _:int) -> = :Foo&.print_add;  (3)
$foo:Foo<(314159);
f_add_display(foo, 1);
f_add_display(314159, 1);  (4)

$f_num:?(_:&Foo) -> :int = :Foo&.num;  (5)
/std cout << "num: " << f_num(foo) << "\n"';

$f_add_display2:?(_:int) -> = :Foo&.print_add\(foo, \1);  (6)
f_add_display2(2);

$f_add_display3:?(_:int) -> = :Foo&.print_add\(&foo, \1);  (7)
f_add_display3(3);

$f_display_obj:?(_:int) -> = :PrintNum<();  (8)
f_display_obj(18);
1 Store a free function
2 Store a lambda
3 Store a call to a member function
4 Error: :Foo constructor is explicit by default
5 Store a call to a data member accessor
6 Store a call to a member function and object
7 Store a call to a member function and object ptr
8 Store a call to a function object

Any

See also: cppreference.com


/std cout << /std boolalpha;
$a:!.(:?) = 1;
/std cout << a.type().name() << ": " << a.(:int) << "\n"';
a = 3.14;
/std cout << a.type().name() << ": " << a.(:double) << "\n"';
a = true;
/std cout << a.type().name() << ": " << a.(:bool) << "\n"';

# Bad cast
try {
  a = 1;
  /std cout << a.(:float) << "\n"';
} catch $e:/std:bad_any_cast {
  /std cout << e.what() << "\n"';
};

# has_value
a = 1;
if a.has_value() {
  /std cout << a.type().name() << "\n"';
};

# reset
a.reset();
if !a.has_value() {
  /std cout << "no value\n";
};

# pointer to contained data
a = 1;
$i:'int = a.&(:int);  (1)
/std cout << i' << "\n"';

a = "hello"s;

# reference
$ra:&! = a.(:&!/std:string);
ra.(1) = "o"';

# immutable reference
/std cout << "a: " << a.(:&/std:string) << "\n"';

# rvalue reference
$b: = (^a).(:^/std:string);

/std cout << "a: " << a.&(:/std:string)' << "\n"';  (2)
/std cout << "b: " << b << "\n"';
1 The & version returns a pointer to the contained value (which may be null)
2 Again using the pointer version

Tuple

See also: cppreference.com


$ get_student:(id:int) -> :.[:double, :char, :/std:string] {
  if id == 0 { -> /std make_tuple(3.8, "A"', "Lisa Simpson")};
  if id == 1 { -> <{2.9, "C"', "Milhouse Van Houten"}};
  if id == 2 { -> <{1.7, "D"', "Ralph Wiggum"}};
  throw :/std:invalid_argument<("id");
}

$student0: = get_student(0);
/std cout << "ID: 0, "
          << "GPA: " << student0.[0] << ", "
          << "grade: " << student0.[1] << ", "
          << "name: " << student0.[2] << "\n"';

$gpa1:!double;
$grade1:!char;
$name1:!/std:string;
(gpa1, _, name1) = get_student(1);  (1)
/std cout << "ID: 1, "
          << "GPA: " << gpa1 << ", "
          << "name: " << name1 << "\n"';

grade1 = "B"';
$student1 := (gpa1, grade1, name1);  (2)

${gpa2, _, name2}: = get_student(2);  (3)
/std cout << "ID: 2, "
          << "GPA: " << gpa2 << ", "
          << "name: " << name2 << "\n"';
1 _ can be used to exclude unused elements
2 Automatically creates a typed tuple from the given elements
3 _ can be used to exclude unused elements

Variant

See also: cppreference.com


$v, w:!|[:!int, :!float];
v = 12;
$i: = v.[:!int];
w = v.[:!int];
w = v.[0];  (1)
w = v;  (2)

v.[:!double];  (3)
v.[3];  (4)

try {
  w.[:!float];  (5)
} catch $_:&/std:bad_variant_access {};

+$/std/literals;
$y:!|[:!/std:string, :!'void] = "abc";  (6)
assert(y.&[:!'void]);  (7)
y = "xyz"s;  (8)
assert(y.&[:!/std:string]);  (9)
1 Same effect as previous line
2 Same effect as previous line
3 Error: no such type in variant
4 Error: no such index in variant
5 w contains int, not float, will throw
6 Will use the second type in the variant
7 Succeeds
8 Will use the first type in the variant
9 Succeeds

Containers

See also: cppreference.com

Arrays

See also: cppreference.com


$a1:.[3]!int <{1, 2, 3};
$a2:.[3]!int <{1, 2, 3};
$a3:.[2]!/std:string <{:/std:string<("a"), "b"};

/std sort(a1.begin(), a1.end());
/std reverse_copy(a2.begin(), a2.end(), :/std:ostream_iterator[:!int]<(/std cout, " "));

/std cout << "\n"';

for $..s:& = a3 {
  /std cout << s << " "';
};

$cube:.[3].[3].[3]!int;  (1)
1 Multi-dimensional array

Appendix A: cigma(1) man page

Synopsis

cigma [-?] [-v] [-V] [-l|-L] [-t out.ast] [-y out.yml] [-h out.hpp] [-p out.cpp] [-c out.cm] [-d out.dot] [-C in.cm | in.cm …​]

Description

This tool primarily tramslates Cigma source code into C++ and, optionally, to other internal formats useful for debugging the translator.

Options

-C, --in-cm=file.cm

Read input in Cigma format from file.cm.

-L, --no-linemarkers

Do not include line information in generated C++. This makes it easier to read.

-l, --linemarkers

Emit line information in generated C++. This is the default.

-t, --out-ast=file.ast

Write output in AST format to file.ast.

-p, --out-cpp=file.cpp

Write output in C++ format to file.cpp (implementation only).

-d, --out-dot=file.dot

Write declaration graph in DOT format to file.dot.

-h, --out-hpp=file.hpp

Write output in C++ format to file.hpp (interface only).

-y, --out-yml=file.yml

Write output in YAML format to file.yml.

-V, --less-verbose

Less verbose mode. Repeat option to reduce verbosity level.

-v, --verbose

Emit verbose output. Repeat this option to increase verbosity level.

in.cm

Any other command line argument is assumed to be a Cigma source file to parse (equivalent to -C).

Exit Status

Zero in case the input was parsed and transformed successfully. Non-zero otherwise.