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) orstd::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
See also: GitHub - tartanllama
See also: Twitter - TartanLlama
$: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 |
Assembler
See also: cppreference.com, felixcloutier.com
Initialization
Initialization in Cigma can be expressed in two ways, depending on the author’s intent:
-
The first form uses
<(…)
syntax and performs direct initialization. -
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
See also: cppreference.com, modernescpp.com
$ 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 |
Class templates
See also: cppreference.com
Function templates
See also: cppreference.com
Member templates
See also: cppreference.com
$[:T]:S {
$[:U].foo:() -> {};
};
$[:T] bar:() -> {
$s:S[:T];
s.foo[:T]();
};
Variable templates
See also: cppreference.com, C++ Weekly
$[: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. ¶