Macros
The macro capabilities of C3 reaches across several constructs: macros (prefixed with @
at invocation), 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
Conditional compilation
Macros
Dynamic scoping
Reference arguments
Use &
in front of a parameter to capture the variable and pass it by reference without having to explicitly use &
and pass a pointer.
(Note that in C++ this is allowed for normal functions, whereas for C3 it is only permitted with macros. Also,
in C3 the captured argument isn’t automatically dereferenced)
First class types
Trailing blocks for macros
First class names
Declaration attributes
Declaration macros
Stringification
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
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. Finally &
is used to implicitly pass a parameter by reference.
@
is required on macros that use #
and &
parameters.
A basic swap:
This expands on usage like this:
Note the necessary &
. Here is an incorrect swap and what it would expand to:
Macro methods
Similar to regular methods a macro may also be associated with a particular type:
See the chapter on functions for more details.
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:
Macros returning values
A macro may return a value, it is then considered an expression rather than a statement:
Calling macros
It’s perfectly fine for a macro to invoke another macro or itself.
The maximum recursion depth is limited to the macro-recursion-depth
build setting.
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:
$vacount
Returns the number of arguments.
$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
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
Returns the argument as an unevaluated expression. Multiple uses will
evaluate the expression multiple times, this corresponds to #
parameters.
$vatype
Returns the argument as a type. This corresponds to $Type
style parameters,
e.g. $vatype(2) a = 2
$varef
Returns the argument as an lvalue. This corresponds to &myref
style parameters,
e.g. *$varef(1) = 123
.
$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 past in
arguments 2, 3 and 4).
Nor is it limited to function arguments, you can also use it with initializers:
Untyped lists
Compile time variables may hold untyped lists. Such lists may be iterated over or implicitly converted to initializer lists: