Macros.cpp
// Macros.cpp
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2007
@import "Ceda/cxObject/MacroUtils.h"
@import "ExampleUtils.h"
#include <vector>
@println
(
'@macros' = @macros
'mMacroDefined(_DEBUG)' = mMacroDefined(_DEBUG)
'mMacroDefined(_UNICODE)' = mMacroDefined(_UNICODE)
)
/*
Note to reader
--------------
This file is intended to be read from start to finish as a tutorial on the xcpp macro expander
It is worthwhile looking at the file generated by xcpp on this file to see the results of the
macro expansion.
*/
///////////////////////////////////////////////////////////////////////////////////////////////////
// Introduction
/*
xcpp provides a powerful macro expanding capability. This can be compared to the #define facility
in C/C++, however it is far more powerful.
The preprocessor directives (such as @def which is used to define a macro) generally begin with
the '@' character. Since this character is not generally meaningful in C/C++ programs, there is
little chance that the xcpp preprocessor will interfere with normal C/C++ code.
*/
// The following defines a macro named "mymacro" with the value 100
// xcpp strips this macro definition out of the generated output.
@def mymacro = 100
// This code references the above macro and therefore xcpp will apply the macro substitution in the
// generated output.
int f1()
{
// return 100;
return mymacro;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Redefining a macro
/*
A macro can be redefined. No warnings are emitted.
*/
@def mymacro = 200
int f2()
{
// return 200;
return mymacro;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Macro arguments
// In similar fashion to C/C++ #defines, xcpp macros can take any number of arguments.
// Note that by default the macro expander performs substitution without regard for the semantics
// of C/C++ programs. The following example shows how inappropriate usage can lead to surprises.
@def add(x,y) = x+y
int f3()
{
// Oops! return 3*5+2;
return 3*add(5,2);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Typing of macro return values
/*
The return type of a macro can be specified. In the following example we stipulate that add(x,y)
always returns an int. This actually causes xcpp to perform calculations at compile time in
order to coerce the return type.
This can have a number of advantages
1. It can avoid the problems with macro expanded expressions needing additional brackets in order
to be processed correctly
2. It helps to self document the intent
3. The xcpp compiler validates the type, and provides useful error messages when type checking
fails.
4. Eager evaluation can allow compilation to run more effiently.
5. The generated code is more succinct.
*/
@def int add(x,y) = x+y
int f4()
{
// return 3*7;
return 3*add(5,2);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Typing of macro arguments
/*
Arguments to macros can be strongly typed. This leads to eager evaluation of the arguments by the
xcpp preprocessor.
The advantages described above for strong typed return values also apply to strongly typed
arguments.
*/
@def multiply(int x,int y) = x*y
int f5()
{
// return 3*4;
return multiply(1+2,4);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
This example shows strong typing of arguments as well as the return value of a macro. This makes
the macro very safe to use, self documenting, efficient and leads to succinct output for the
subsequent C/C++ compiler.
*/
@def int multiply(int x,int y) = x*y
int f6()
{
// return 12;
return multiply(1+2,4);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// @def
/*
The following is a more formal definition of the @def directive
@def [return-type] x [([type] a1,...,[type] an)] = y
Defines a macro named x
This directive macro expands into nothing
The return type is optional. If given it must be bool, char, int, double or string.
x is the name of the macro and must be an identifier.
The argument list is optional.
Each formal argument must be an identifier. Formal argument names cannot be repeated.
A formal argument can optionally be preceded by a type which must be bool, char, int,
double or string.
When a formal argument is not typed, the formal argument binds directly to the text
provided in the invocation of the macro.
When a formal argument is typed, the text provided for the formal argument in the
invocation of the macro is first macro expanded then evaluated as an expression that
must be implicit convertible to the type of the formal argument. The formal argument
binds to the string representation of the evaluated result.
If no return type is provided then when x is invoked, x macro expands into the result
of macro expanding y.
If a return type is provided then after macro expanding y, it is evaluated as an
expression that must be implicit convertible to the return type. The invocation of x
is then substituted by the string representation of the evaluated result.
The macro named x is added to the local namespace. This may replace an existing
definition of x in the local namespace. Note that within the body of a macro, the
local namespace includes the names of the formal arguments of that macro.
A number of directives introduce a new namespace.
There is no support for overloading of macros - even on arity.
*/
///////////////////////////////////////////////////////////////////////////////////////////////////
// Rules for delimiting
/*
In the directive
@def x = y
y is the "substitution string". This is delimited in a number of different ways. This is
demonstrated with some examples
1. @def x = h {[(
The substitution string is on the same line as the '=' and doesn't begin with a '{'. In this
case the substitution string begins with the first non-white space character after the '='
and includes all characters (including white space) up to but not including the linefeed.
2. @def x =
{
{} {{ }}
}
In this case, after the '=' there is possibly some white space before reaching the linefeed.
It is required that the next token will be a '{'. The extent of the substitution string is
determined by counting curly brackets (i.e. incrementing the count for each '{' and decrementing
the count for each '}') until the count returns to zero.
The substitution string is the block of text without the outer-most curly brackets.
3. @def x = { ok{} }
A '{' token is read after and on the same line as the '='. The subsitution string is scanned
assuming curly bracket counting until the count returns to zero. The substitution string is
the block of text without the outer-most curly brackets. All white space between the braces is
significant
*/
// In this example w1,w2,w3 are all equivalent.
@def w1 = 5
@def w2 = {5}
@def w3 =
{
5
}
int f7()
{
// return 5+5+5;
return w1+w2+w3;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// @assert()
/*
The @assert(x) directive macro expands x then evalulates it as an expression that must be implicit
convertible to a bool. If false then a fatal error "Assertion failed" is thrown by the xcpp
preprocessor and compilation fails.
The @assert(x) directive is stripped from the generated output. I.e. it macro expands into nothing.
*/
@assert(1 < 2)
///////////////////////////////////////////////////////////////////////////////////////////////////
// @if-@else directives
/*
@if-@else directives can be used for conditional macro expansion.
Most generally the form is
@if (b1) {x1}
@elseif (b2) {x2}
@elseif (b3) {x3}
...
@elseif (bn) {xn}
@else {y}
There may be 0 or more @elseif directives. The @else part is optional.
@if (b) {x}
Macro expands to nothing or the result of macro expanding y depending on whether x is true
or false. x is macro expanded before evaluating as an expression that should be implicit
convertible to bool.
@if (x) {y} @else {z}
Macro expands to the result of macro expanding y or z depending on whether x is true or
false. x is macro expanded before evaluating as an expression that should be implicit
convertible to bool.
*/
@def min(x,y) =
{
@if(x < y) {x} @else {y}
}
@assert( min(min(3,7),min(1,6)) == 1 )
///////////////////////////////////////////////////////////////////////////////////////////////////
// @str()
/*
The @str(x) directive expands into a double quoted string obtained by first macro expanding x then
converting it to a double quoted string. Non-ascii characters and double quote characters are
escaped.
*/
@assert( @str(1+2) == "1+2" )
@assert( @str(The minimum of 3,4 is min(3,4)) == "The minimum of 3,4 is 3" )
///////////////////////////////////////////////////////////////////////////////////////////////////
// @strx()
/*
@strx() is the same as @str() except that linefeeds cause the string to be broken up into
separate strings. This is useful given that the C/C++ compiler concatenates adjacent string
literals.
The strings are formatted into a nicely tabbed block of code ready to be compiled by the C/C++
compiler.
*/
const char* strx_test()
{
static const char* s =
@strx
(
This is a sentence
stating that the
minimum of 3,4 is
min(3,4)
);
return s;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// @unstr()
/*
To process @unstr(x), x is macro expanded and this is assumed to produce a valid single or double
quoted string. @unstr(x) macro expands into a version of the string with the quotes
removed. Also characters that were escaped are "unescaped". Eg \n is replaced by a real linefeed
character.
*/
@unstr("//") This is a C++ style comment!
// This is a C++ style comment!
///////////////////////////////////////////////////////////////////////////////////////////////////
// Nested macro definitions
/*
Macro definitions can be nested. The following example shows how macro m1 defines a local macro
called max. Local macros are not accessible outside the scope of the containing macro.
*/
@def m1(x) =
{
@def max(a,b) = @if(a<b) {b} @else {a}
max(5,x)
}
@assert( m1(1) == 5 )
@assert( m1(20) == 20 )
// This shows that the nested macro 'max' is not available at global scope
@assert( @str(max(1,2)) == "max(1,2)" )
///////////////////////////////////////////////////////////////////////////////////////////////////
// @scope
/*
The @scope{x} directive defines a local scope for macro definitions. x is macro expanded as if no
@scope directive was defined.
It can be very important to limit the scope of macros, to avoid accidental invocation outside
their intended usage.
It is generally a risky proposition to use simple, common identifiers for macro names unless they
are local to a rather limited scope.
*/
@scope
{
@def m(T) =
{
T myfn(T x1, T x2)
{
return x1 < x2 ? x1 : x2;
}
}
m(int)
m(double)
}
// This shows that the macro 'm' is not available outside the scope it was defined in
@assert( @str(m(char)) == "m(char)" )
///////////////////////////////////////////////////////////////////////////////////////////////////
// @runpython
/*
The @runpython(x) directive macro expands x then runs it under the python interpreter.
The definitions of functions and variables are not lost after the directive is invoked - they are
available to subsequent directives.
The directive itself is stripped from the generated output. I.e. it macro expands into nothing.
*/
@runpython
{
# Fibonacci series:
# the sum of two elements defines the next
def getfibnumbers(max):
a,b = 0,1
s = []
while b < max:
s.append(b)
a,b = b,a+b
return s
}
// Note how the (previously defined) min(4,8) macro expands to 4 before running under the python
// interpreter.
@runpython
{
print 'This is written to stdio when xcpp processes this file'
for i in range( min(4,8) ):
print i
print getfibnumbers(10)
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// @defpython
/*
The @defpython directive is of the form
@defpython macroname(a1,...,an) = y
It defines a macro that executes python code y (after y is macro expanded)
The directive itself is stripped from the generated output. I.e. it macro expands into nothing.
*/
@defpython getfib(int x) =
{
# A python expression that evaluates to an int, float or string
str(getfibnumbers(x))
}
@assert( @str(getfib(6)) == "[1, 1, 2, 3, 5]" )
///////////////////////////////////////////////////////////////////////////////////////////////////
// @@
/*
The @@ directive expands into nothing, but nevertheless acts as a delimiter for the lexical scanner.
For example putting @@ in the middle of an identifier causes the lexical scanner to see it has two
distinct tokens.
*/
@scope
{
@def x = 1
@def y = 2
@def xy = 3
@assert( @str(xy) == "3" )
@assert( @str(x@@y) == "12" )
}
@assert ( @str(x@@min(1,2)) == "x1" )
///////////////////////////////////////////////////////////////////////////////////////////////////
// @quote
/*
The @quote(x) directive macro expands literally to x (i.e. without macro expanding x). This is
useful when we specifically want to turn off macro expansion
*/
@assert( @str(@quote(min(3,4)) = min(3,4)) == "min(3,4) = 3" )
///////////////////////////////////////////////////////////////////////////////////////////////////
// @(x)
/*
The @(x) directive macro expands to the string obtained by macro expanding x, evaluating it as an
expression then (explicit) converting the result to a string.
*/
@assert( @str(@(1+2)) == "3" )
///////////////////////////////////////////////////////////////////////////////////////////////////
// @[x]
/*
The @[x] directive macro expands to the result of macro expanding the result of macro expanding x
(i.e. macro expansions are applied twice)
*/
@def v1 = 10
@assert( @str(v@@1) == "v1" )
@assert( @str(@[v@@1]) == "10" )
///////////////////////////////////////////////////////////////////////////////////////////////////
// Recursive macros
/*
Macros are allowed to call themselves recursively, as long as they terminate! The following
example shows how we can calculate n! efficiently at compile time
This example makes use of strong typing on both the argument and return type.
*/
// Compute n!. Note the recursion and the strong typing
@def int factorial(int n) =
{
@if (n <= 1) {1} @else {n*factorial(n-1)}
}
int fact1()
{
// return 120;
return factorial(5);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
It is illustrative to see what happens if we don't specify the return type on the factorial macro.
*/
@def factorial(int n) =
{
@if (n <= 1) {1} @else {n*factorial(n-1)}
}
int fact2()
{
// return 5*4*3*2*1;
return factorial(5);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
If we don't strongly type the argument then we end up with an erroneous definition of n!, because
of lack of bracketting
*/
@def factorial(n) =
{
@if (n <= 1) {1} @else {n*factorial(n-1)}
}
int fact3()
{
// Oops! return 5*5-1*5-1-1*5-1-1-1*1;
return factorial(5);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Backquoted macro arguments
/*
An argument to a macro can be backquoted in order to ensure it is parsed as a single indivisible
argument to bind to the formal argument of the macro.
It is necessary to backquote the argument std::vector<T,A> because it contains commas, and the
macro expander doesn't count angled brackets to delimit terms.
*/
@scope
{
@def fn(type) =
{
ceda::xostream& operator<<(ceda::xostream& os, const type& v)
{
os << '[';
for (typename type::const_iterator p = v.begin() ; p != v.end() ; ++p)
{
if (p != v.begin()) os << ',';
os << *p;
}
os << ']';
return os;
}
}
template <typename T,typename A>
fn( `std::vector<T,A>` )
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Tables
/*
The following example shows an interesting technique, where a table of information is specified
in macro tbl
@def tbl =
{
m(one,4,5)
m(two,-4,7)
m(seven,8,1)
}
This can be regarded as a table of records
a b c
--------------------
one 4 5
two -4 7
seven 8 1
These records can be processed in any number of ways, simply by specifying a local macro m
*/
struct X
{
const char* a;
int b;
int c;
int d;
};
@def WriteXArray(name,tbl,d) =
{
@def m(a,b,c) =
{
{ @str(a),b,c,10 },
}
static X name[] =
{
@[tbl]
};
}
/*
Expands into ...
static X x[] =
{
{ "one",4,5,10 },
{ "two",-4,7,10 },
{ "seven",8,1,10 },
};
*/
@scope
{
@def tbl =
{
m(one,4,5)
m(two,-4,7)
m(seven,8,1)
}
WriteXArray(x,tbl,10)
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Tables 2
/*
Expands into ...
static X y[] =
{
{ "one",4,5,10 },
{ "two",-4,7,10 },
{ "seven",8,1,10 },
}
*/
@scope
{
@def tbl(m()) =
{
m(one,4,5)
m(two,-4,7)
m(seven,8,1)
}
@def m(a,b,c) =
{
{ @str(a),b,c,10 },
}
static X y[] =
{
tbl(m)
};
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// @let directive
@scope
{
/*
// Macro m can be redefined
@def m = "hello world"
const char* f_let() { return m; }
@def int m = 200
int g_let() { return m; }
*/
/*
// Demonstrate infinite loop
@def int m = 1
@def int m = m+1
int h_let() { return m; }
*/
/*
@if (m > 0)
{
@def m = m+1
}
int h_let() { return m; }
*/
/*
@let int m = 0
@let m = m+1
int h_let() { return m; }
@if (m > 0) @let m = m+1
int i_let() { return m; }
@let string m = "hello" + " "
@let m = @str(m) + 'world'
@let m := m !!!
const char* f_let() { return @str(m); }
@def double e = exp(1)
double ff() { return e+1; }
int A[] =
{
@(2*2*2*2), @(2^^4), @(1 << 4),
@(0xabcd & 0xf) , @(3 | 5), @(27 % 4),
@("x" == "y"), @("x" < "y"+'z'),
@(1 < 2 && 2 < 4), @( !(1+1 < 2) ? 10 : 20),
};
@def m(x,y,b) =
{
if (x < y)
{
return b;
}
}
@def loop(i,n,body) =
{
for (int i=0 ; i < n ; ++i)
{
body
}
}
bool fff(int* x, int nx, int* y, int ny)
{
loop(i,nx,loop(j,ny,m(x[i],y[j],true)))
return false;
}
*/
/*
// Macros are expanded in the context of the caller
@def n = 1
@def m = n
@def n = 2
int f() { return m; }
@def n = 3
int g() { return m; }
*/
/*
@def leftBrace = @unstr('{')
@def rightBrace = @unstr('}')
@def m = leftBrace@@leftBrace
const char* f()
{
//{m }}
{m}}}
return @str(m);
}
*/
/*
@def int a = 1
@if
(
@def a = true
a
)
{
@def b = 2
int c = 10;
int f() { return @(@def c = {3} a+b+c); }
}
*/
/*
if (c1 == 'r') c2 = '\r'; // Carriage return
else if (c1 == 'n') c2 = '\n'; // Linefeed
else if (c1 == 'a') c2 = '\a'; // Alert
else if (c1 == 'b') c2 = '\b'; // Backspace
else if (c1 == 'f') c2 = '\f'; // Formfeed
else if (c1 == 't') c2 = '\t'; // Horizontal tab
else if (c1 == 'v') c2 = '\v'; // Vertical tab
else if (c1 == '0') c2 = '\0';
else if (c1 == '\"') c2 = c1;
else if (c1 == '\\') c2 = c1;
else if (c1 == '\'') c2 = c1;
else if (c1 == '?') c2 = c1;
*/
/*
@def a = @unstr('\a')
@def b = @unstr('\b')
@def f = @unstr('\f')
@def n = @unstr('\n')
@def r = @unstr('\r')
@def t = @unstr('\t')
@def v = @unstr('\v')
@def ff = @unstr('\xff')
@def z = @unstr('\0')
//@def q = @unstr('"')
const char* s = @str(a b f n r t v ff z '\0' "ok");
*/
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// @for directive
void mytest(int i,int j = 0) {}
void example1()
{
@for (i in [1,2,3])
{
mytest(i);
}
@for ((i) in [(1),(2),(3)])
{
mytest(i);
}
@for ((i,j) in [(1,7),(2,-1),(3,4)])
{
mytest(i,j);
}
const char* s =
@strx
(
@def makelist(int n) =
{
@if(n>1) {makelist(n-1),n} @else {1}
}
@for ( i in [makelist(10)] )
{
@for ( j in [makelist(i)] )
{
i + j = @(i+j)
}
}
);
}
/*
@for ((type,val) in [(int,10), (char, 'c'), (double, 2.71828)])
{
void test(type x)
{
Tracer() << @str(adding type val) << '\n';
type y = val;
assert(x + y > 0);
}
}
void UnitTests()
{
@for ((type,val) in [(int,10), (char, 'c'), (double, 2.71828)])
{
Tracer() << @str(Testing with type val) << '\n';
type x@@type = val;
RunUnitTest(x@@type);
}
}
*/
///////////////////////////////////////////////////////////////////////////////////////////////////
/*
The ceda macro expander now allows for @$ macros - and they are available in c++, xcpp, xcpj and
xcws files. They are parameterless public file scope macros that are (only) expanded inside
literal strings - when they have the form " .... $(name) .... ".
Example:
@ $h = "Hello"
@ $w = "$(h) world"
@assert("$(w)" == "Hello world")
Any white space can appear between the tokens of a @$ macro definition. For example:
@
$
h
=
"Hello"
is equivalent to
@$h = "Hello"
The value of a macro can either be a literal double quoted string or else an expression in
parentheses that evaluates to a double quoted string. E.g.
@$h = ((2 > 1) ? "Hell" + "o" : "")
Note that within xcpp, xcpj, xcws files the leading '@' is usually optional because these
languages natively support $ macros. However when there is no leading @ they are invisible
to the preprocessor. So for example the following won't work in an xcpp file
$X = "1" // <------- needs @ to be seen by the preprocessor
@if ("$(X)" == "1")
{
etc
}
I intend to avoid this issue down the track by allowing the macro preprocessor to have a mode
where it doesn't require the leading '@' token.
*/
@
$
h
=
( (2 > 1) ? "Hell" + "o" : "" )
@ $w = "$(h) world"
@assert("$(w)" == "Hello world")
///////////////////////////////////////////////////////////////////////////////////////////////////
// Macro utils
void foo()
{
@def m =
{
[
@str(mToIdentifier( `xvector<std::pair<int32,float64> >` )),
@str(mStringLength(hello )),
@str(mGetSubString( h ello,0,5)),
@str(mGetCharInString(hello,2)),
@str(mToUpper(hello)),
@str(mToLower(heLLo)),
@str(mCapitaliseFirstLetter(hello)),
@str(mEatLeadingWhitespace(@unstr(" hello this is a test "))),
@str(mEatTrailingWhitespace(@unstr(" hello this is a test "))),
@str(mCentreJustify(hello,10)),
@str(mLeftJustify(hello,10)),
@str(mRightJustify(hello,10)),
@str(mCountSubStrings(one two one one two three,one)),
@str(mExpandTabsInString(@unstr("\tone\ttwo\t"),2)),
@str(mFindLeftMostSubString(bcbacbcabcdeabcdeabc,abc)),
@str(mFindRightMostSubString(bacbcbacbcabcdeabcdeabc,bac)),
@str(mIsSubStringPrefix(hello,he)),
@str(mIsSubStringSuffix(hello,lo)),
@str(mReplaceInString(this is a test,is,was)),
@str(mStripLeadingAndTrailingWhitespace( ok )),
@str(mPadWithLeadingZeros(10,5)),
@str(mIsAlnumString(Hello123)),
@str(mIsAlphaString(Hello)),
@str(mIsDigitString(1234)),
@str(mIsLowerCaseString(hello)),
@str(mIsWhiteSpaceString( )),
@str(mSplitLines(@unstr("\nhello\none\ntwo\n\n"),true)),
@str(mSplitWithDelimiter(one two three,char(' ')))
]
}
// "stdvector_L_stdpair_L_int32float64_R__R_"
// "5"
// "h ell"
// "l"
// "HELLO"
// "hello"
// "Hello"
// "hello this is a test "
// " hello this is a test"
// " hello "
// "hello "
// " hello"
// "3"
// " one two "
// "7"
// "5"
// "1"
// "1"
// "thwas was a test"
// "ok"
// "00010"
// "1"
// "1"
// "1"
// "1"
// "0"
// "[\n,hello\n,one\n,two\n,\n]"
// "[one,two,three]"
@for (x in m)
{
@unstr("//") x
}
const char* s =
@str(
@for (x in m)
{
x
}
);
}
/*
Directives
----------
@scope {} Defines a new scope.
Useful for localising macros.
@print(x)
x is macro expanded then written to stdout
@println(x)
Same as @print(x) except that a line-feed is written after x.
@def [return-type] x [([type] a1,...,[type] an)] = y
Defines a macro named x
This directive macro expands into nothing
The return type is optional. If given it must be bool, char, int, double or string.
x is the name of the macro and must be an identifier.
The argument list is optional.
Each formal argument must be an identifier. Formal argument names cannot be repeated.
A formal argument can optionally be preceded by a type which must be bool, char, int,
double or string.
When a formal argument is not typed, the formal argument binds directly to the text
provided in the invocation of the macro.
When a formal argument is typed, the text provided for the formal argument in the
invocation of the macro is first macro expanded then evaluated as an expression that
must be implicit convertible to the type of the formal argument. The formal argument
binds to the string representation of the evaluated result.
If no return type is provided then when x is invoked, x macro expands into the result
of macro expanding y.
If a return type is provided then after macro expanding y, it is evaluated as an
expression that must be implicit convertible to the return type. The invocation of x
is then substituted by the string representation of the evaluated result.
The macro named x is added to the local namespace. This may replace an existing
definition of x in the local namespace. Note that within the body of a macro, the
local namespace includes the names of the formal arguments of that macro.
A number of directives introduce a new namespace.
There is no support for overloading of macros - even on arity.
@runpython(x)
This directive macro expands into nothing
Macro expands x then runs it under the python interpreter.
@defpython macroname(a1,...,an) = y
This directive macro expands into nothing
Defines a macro that executes python code y (after y is macro expanded)
@str(x) :
Macro expands into a double quoted string obtained by first macro expanding x
then converting it to a double quoted string. Non ascii characters and double quote
characters are escaped.
@strx(x) Same as @str(x) except that linefeeds cause the string to be broken up into separare
strings, under the assumption of the C++ compiler which concatenates adjacent string
literals.
@unstr(x)
x is macro expanded and this is assumed to produce a valid single or double quoted string.
@unstr(x) macro expands into a version of the string with the double quotes removed. Also
characters that were escaped are "unescaped". Eg \n is replaced by a real linefeed character.
@quote(x):
Macro expands to x (i.e. without macro expanding x)
@@
Macro expands to nothing
@(x)
Macro expands to the string obtained by macro expanding x, evaluating it as an
expression then (explicit) converting the result to a string.
@[x]
Macro expands to the result of macro expanding the result of macro expanding x
(i.e. macro expansions are applied twice)
@if(x) {y}
Macro expands to nothing or the result of macro expanding y depending on whether x is true
or false. x is macro expanded before evaluating as an expression that should be implicit
convertible to bool.
@if(x) {y} @else {z}
Macro expands to the result of macro expanding y or z depending on whether x is true or
false. x is macro expanded before evaluating as an expression that should be implicit
convertible to bool.
@assert(x)
This directive macro expands into nothing
Macro expands x then evalulates it as an expression that must be implicit convertible to
a bool. If false then a fatal error "Assertion failed" is thrown.
@assertfails(x)
This directive macro expands into nothing
Verifies that macro expansion of x generates a macro expansion error. The error is written
to stdout.
Throws an error if macro expansion of x didn't generate an error.
@fail(x)
Throws an error with description string obtained by macro expanding x then evalulating
as an expression that must be implicit convertible to a string.
@let type x = y
The type must be bool, char, int, double or string.
x is an identifier.
This creates a variable called x in the current scope. y is an expression that is
macro expanded and evaluated. It must be implicit convertible to the type of x. This
is used to initialise the value of x.
Throws an error if there is already a macro or variable of the same name in that scope.
@let x = y
x is an identifier.
A variable named x must have already been defined (either in this scope or a parent
scope). The hiding rule applied - i.e. this will bind to the most local instance.
This reassigns to the value of x. y is an expression that is macro expanded and
evaluated. It must be implicit convertible to the type of x.
@let [string] x := y
When x is a string type this special form of assignment is supported (using :=).
x is assigned the result of macro expanding y.
Note that y is not evaluated as an expression.
@for i in [x,y,z+1] {x}
Macro expands {x} for each value of i in the list
@for (i,j,k) in [ (1+4,2,3), (9,x,3) ] {x}
Macro expands {x} for each binding of i,j,k to respective elements of tuples in the
list
*/
/*
Wish list
---------
@for (int i,j,k) in [ (1+4,2,3), (9,x,3) ] {x}
i is typed and therefore causes expression evaluation and coercion into an
int.
*/