Macros
The macro capabilities of C3 reaches across several constructs:
macros, generic functions, generic modules, and compile time variables
(prefixed with $
), macro compile time execution (using $if
, $for
, $foreach
, $switch
) and attributes.
A quick comparison of C and C3 macros
Section titled “A quick comparison of C and C3 macros”Conditional compilation
Section titled “Conditional compilation”// C Macro#if defined(x) && Y > 3int z;#endif
// C3 Macro$if $defined(x) && Y > 3: int z;$endif
// orint z @if($defined(x) && Y > 3);
Macros
Section titled “Macros”// C Macro#define M(x) ((x) + 2)#define UInt32 unsigned int
// Use:int y = M(foo() + 2);UInt32 b = y;
// C3 Macromacro m(x){ return x + 2;}alias UInt32 = uint;
// Use:int y = m(foo() + 2);UInt32 b = y;
Dynamic scoping
Section titled “Dynamic scoping”// C Macro#define Z() ptr->x->y->zint x = Z();
// C3 Macro... currently no corresponding functionality ...
Expression arguments
Section titled “Expression arguments”// C Macro#define M(x, y) x = 2 * (y);...M(x, 3);
// C3 Macromacro @m(#x, y){ #x = 2 * y;}...@m(x, 3);
First class types
Section titled “First class types”// C Macro#define SIZE(T) (sizeof(T) + sizeof(int))
// C3 Macromacro size($Type){ return $Type.sizeof + int.sizeof;}
Trailing blocks for macros
Section titled “Trailing blocks for macros”// C Macro#define FOR_EACH(x, list) \for (x = (list); x; x = x->next)
// Use:Foo *it;FOR_EACH(it, list){ if (!process(it)) return;}
// C3 Macromacro @for_each(list; @body(it)){ for ($typeof(list) x = list; x; x = x.next) { @body(x); }}
// Use:@for_each(list; Foo* x){ if (!process(x)) return;}
First class names
Section titled “First class names”// C Macro#define offsetof(T, field) (size_t)(&((T*)0)->field)
// C3 Macromacro usz @offset($Type, #field){ $Type* t = null; return (usz)(uptr)&t.#field;}
Declaration attributes
Section titled “Declaration attributes”// C Macro#define PURE_INLINE __attribute__((pure)) __attribute__((always_inline))int foo(int x) PURE_INLINE { ... }
// C3 Macroattrdef @NoDiscardInline = { @nodiscard @inline };fn int foo(int) @NoDiscardInline { ... }
Declaration macros
Section titled “Declaration macros”// C Macro#define DECLARE_LIST(name) List name = { .head = NULL };// Use:DECLARE_LIST(hello)
// C3 Macro... currently no corresponding functionality ...
Stringification
Section titled “Stringification”// C Macro#define CHECK(x) do { if (!x) abort(#x); } while(0)
// C3 Macromacro @check(#expr){ if (!#expr) abort($stringify(#expr));}
Top level evaluation
Section titled “Top level evaluation”Script languages, and also upcoming languages like Jai, usually have unbounded top level evaluation. The flexibility of this style of meta programming has a trade-off in making the code more challenging to understand.
In C3, top level compile time evaluation is limited to @if
attributes to conditionally enable or
disable declarations. This makes the code easier to read, but at the cost of expressive power.
Macro declarations
Section titled “Macro declarations”A macro is defined using macro <name>(<parameters>)
. All user defined macros use the @ symbol if they use the $
or #
parameters.
The parameters have different sigils:
$
means compile time evaluated (constant expression or type). #
indicates an expression that is not yet evaluated,
but is bound to where it was defined. @
is required on macros that use #
parameters or trailing macro bodies.
A basic swap:
<* @checked $defined(#a = #b, #b = #a)*>macro void @swap(#a, #b){ var temp = #a; #a = #b; #b = temp;}
This expands on usage like this:
fn void test(){ int a = 10; int b = 20; @swap(a, b);}// Equivalent to:fn void test(){ int a = 10; int b = 20; { int __temp = a; a = b; b = __temp; }}
Note the necessary #
. Here is an incorrect swap and what it would expand to:
macro void badswap(a, b){ var temp = a; a = b; b = temp;}
fn void test(){ int a = 10; int b = 20; badswap(a, b);}// Equivalent to:fn void test(){ int a = 10; int b = 20; { int __a = a; int __b = b; int __temp = __a; __a = __b; __b = __temp; }}
Macro methods
Section titled “Macro methods”Similar to regular methods a macro may also be associated with a particular type:
struct Foo { ... }
macro Foo.generate(&self) { ... }Foo f;f.generate();
See the chapter on functions for more details.
Capturing a trailing block
Section titled “Capturing a trailing block”It is often useful for a macro to take a trailing compound statement as an argument. In C++ this pattern is usually expressed with a lambda, but in C3 this is completely inlined.
To accept a trailing block, ; @name(param1, ...)
is placed after declaring the regular macro parameters.
Here’s an example to illustrate its use:
<* A macro looping through a list of values, executing the body once every pass.
@require $defined(a.len) && $defined(a[0])*>macro @foreach(a; @body(index, value)){ for (int i = 0; i < a.len; i++) { @body(i, a[i]); }}
fn void test(){ double[] a = { 1.0, 2.0, 3.0 }; @foreach(a; int index, double value) { io::printfn("a[%d] = %f", index, value); };}
// Expands to code similar to:fn void test(){ double[] a = { 1.0, 2.0, 3.0 }; { double[] __a = a; for (int __i = 0; __i < __a.len; __i++) { int __index = __i; double __value = __a[__i]; io::printfn("a[%d] = %f", __index, __value); } }}
Macros returning values
Section titled “Macros returning values”A macro may return a value, it is then considered an expression rather than a statement:
macro square(x){ return x * x;}
fn int getTheSquare(int x){ return square(x);}
fn double getTheSquare2(double x){ return square(x);}
Calling macros
Section titled “Calling macros”It’s perfectly fine for a macro to invoke another macro or itself.
macro square(x) { return x * x; }
macro squarePlusOne(x){ return square(x) + 1; // Expands to "return x * x + 1;"}
The maximum recursion depth is limited to the macro-recursion-depth
build setting.
Macro vaargs
Section titled “Macro vaargs”Macros support the typed vaargs used by C3 functions: macro void foo(int... args)
and macro void bar(args...)
but it also supports a unique set of macro vaargs that look like C style vaargs: macro void baz(...)
To access the arguments there is a family of $va-* built-in functions to retrieve the arguments:
macro compile_time_sum(...){ var $x = 0; $for var $i = 0; $i < $vacount; $i++: $x += $vaconst[$i]; $endfor return $x;}$if compile_time_sum(1, 3) > 2: // Will compile to $if 4 > 2 ...$endif
$vacount
Section titled “$vacount”Returns the number of arguments.
$vaarg
Section titled “$vaarg”Returns the argument as a regular parameter. The argument is guaranteed to be evaluated once, even if the argument is used multiple times.
$vaconst
Section titled “$vaconst”Returns the argument as a compile time constant, this is suitable for
placing in a compile time variable or use for compile time evaluation,
e.g. $foo = $vaconst(1)
. This corresponds to $
parameters.
$vaexpr
Section titled “$vaexpr”Returns the argument as an unevaluated expression. Multiple uses will
evaluate the expression multiple times, this corresponds to #
parameters.
$vatype
Section titled “$vatype”Returns the argument as a type. This corresponds to $Type
style parameters,
e.g. $vatype(2) a = 2
$vasplat
Section titled “$vasplat”$vasplat
allows you to paste the varargs in the call into another call. For example,
if the macro was called with values "foo"
and 1
, the code foo($vasplat)
, would become foo("foo", 1)
.
You can even extract provide a range as the argument: $vasplat[2..4]
(in this case, this would paste in
arguments 2, 3 and 4).
Nor is it limited to function arguments, you can also use it with initializers:
int[*] a = { 5, $vasplat[2..], 77 };
Untyped lists
Section titled “Untyped lists”Compile time variables may hold untyped lists. Such lists may be iterated over or implicitly converted to initializer lists:
var $a = { 1, 2 };$foreach $x : $a: io::printfn("%d", $x);$endforeachint[2] x = $a;io::printfn("%s", x);io::printfn("%s", $a[1]);// Will print// 1// 2// [1, 2]// 2