Skip to content

Commit

Permalink
Fixes to compiletime.
Browse files Browse the repository at this point in the history
  • Loading branch information
lerno committed Mar 9, 2024
1 parent fe4f657 commit b0ae6ee
Showing 1 changed file with 126 additions and 107 deletions.
233 changes: 126 additions & 107 deletions src/content/docs/references/docs/compiletime.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,55 +23,61 @@ Mutable compile time variables are *not* allowed in the global scope.

`$if <const expr>:` takes a compile time constant value and evaluates it to true or false.

macro foo($x, $y)
{
$if $x > 3:
$y += $x * $x;
$else
$y += $x;
$endif
}

const int FOO = 10;

fn void test()
{
int a = 5;
int b = 4;
foo(1, a); // Allowed, expands to a += 1;
// foo(b, a); // Error: b is not a compile time constant.
foo(FOO, a); // Allowed, expands to a += FOO * FOO;
}
```c3
macro @foo($x, &y)
{
$if $x > 3:
*y += $x * $x;
$else
*y += $x;
$endif
}
const int FOO = 10;
fn void test()
{
int a = 5;
int b = 4;
@foo(1, a); // Allowed, expands to a += 1;
// @foo(b, a); // Error: b is not a compile time constant.
@foo(FOO, a); // Allowed, expands to a += FOO * FOO;
}
```

For switching between multiple possibilities, use `$switch`.

macro foo($x, $y)
{
$switch ($x)
$case 1:
$y += $x * $x;
$case 2:
$y += $x;
$case 3:
$y *= $x;
$default:
$y -= $x;
$endif
}
```c3
macro @foo($x, &y)
{
$switch ($x)
$case 1:
*y += $x * $x;
$case 2:
*y += $x;
$case 3:
*y *= $x;
$default:
*y -= $x;
$endswitch
}
```

Switching without argument is also allowed, which works like an if-else chain:

macro foo($x, $y)
{
$switch
$case $x > 10:
$y += $x * $x;
$case $x < 0:
$y += $x;
$default:
$y -= $x;
$endif
}
```c3
macro @foo($x, &y)
{
$switch
$case $x > 10:
*y += $x * $x;
$case $x < 0:
*y += $x;
$default:
*y -= $x;
$endswitch
}
```

### Loops using $foreach and $for

Expand All @@ -80,43 +86,47 @@ matches the behaviour of `foreach`.

Compile time looping:

macro foo($a)
{
$for (var $x = 0; $x < $a; $x++)
io::printfn("%d", $x);
$endfor
}

fn void test()
{
foo(2);
// Expands to ->
// io::printfn("%d", 0);
// io::printfn("%d", 1);
}
```c3
macro foo($a)
{
$for (var $x = 0; $x < $a; $x++)
io::printfn("%d", $x);
$endfor
}
fn void test()
{
foo(2);
// Expands to ->
// io::printfn("%d", 0);
// io::printfn("%d", 1);
}
```

Looping over enums:

macro foo_enum($SomeEnum)
{
$foreach ($x : $SomeEnum.values)
io::printfn("%d", (int)$x);
$endforeach
}

enum MyEnum
{
A,
B,
}

fn void test()
{
foo_enum(MyEnum);
// Expands to ->
// io::printfn("%d", (int)MyEnum.A);
// io::printfn("%d", (int)MyEnum.B);
}
```c3
macro foo_enum($SomeEnum)
{
$foreach ($x : $SomeEnum.values)
io::printfn("%d", (int)$x);
$endforeach
}
enum MyEnum
{
A,
B,
}
fn void test()
{
foo_enum(MyEnum);
// Expands to ->
// io::printfn("%d", (int)MyEnum.A);
// io::printfn("%d", (int)MyEnum.B);
}
```

An important thing to note is that the content of the `$foreach` or `$for` body must be at least a complete statement.
It's not possible to compile partial statements.
Expand All @@ -126,24 +136,28 @@ It's not possible to compile partial statements.
If a macro only takes compile time parameters, that is only `$`-prefixed parameters, and then does not generate
any other statements than returns, then the macro will be completely compile time executed.

macro @test($abc)
{
return $abc * 2;
}
```c3
macro @test($abc)
{
return $abc * 2;
}
const int MY_CONST = @test(2); // Will fold to "4"
const int MY_CONST = @test(2); // Will fold to "4"
```

This constant evaluation allows us to write some limited compile time code. For example, this
macro will compute Fibonacci at compile time:

macro long @fib(long $n)
{
$if $n <= 1:
return $n;
$else
return @fib($n - 1) + @fib($n - 2);
$endif
}
```c3
macro long @fib(long $n)
{
$if $n <= 1:
return $n;
$else
return @fib($n - 1) + @fib($n - 2);
$endif
}
```

It is important to remember that if we had replaced `$n` with `n` the compiler would have complained. `n <= 1`
is not be considered to be a constant expression, even if the actual argument to the macro was a constant.
Expand All @@ -153,25 +167,28 @@ This limitation is deliberate, to offer control over what is compiled out and wh

At the top level, conditional compilation is controlled using with `@if` attributes on declarations

fn void foo_win32() @if(env::WIN32)
{
/* .... */
}
```c3
fn void foo_win32() @if(env::WIN32)
{
/* .... */
}
struct Foo
{
int a;
int b @if(env::NO_LIBC);
}
struct Foo
{
int a;
int b @if(env::NO_LIBC);
}
```

The argument to `@if` must be possible to resolve to a constant at compile time. This means that argument
may also be a compile time evaluated macro:

macro bool @foo($x) => $x > 2;

int x @if(@foo(5)); // Will be included
int y @if(@foo(0)); // Will not be included
```c3
macro bool @foo($x) => $x > 2;
int x @if(@foo(5)); // Will be included
int y @if(@foo(0)); // Will not be included
```

#### Evaluation order of top level conditional compilation

Expand All @@ -189,10 +206,12 @@ rely on this ordering.

At compile time, full type information is available. This allows for creation of reusable, code generating, macros for things
like serialization.

usz foo_alignment = Foo.alignof;
usz foo_member_count = Foo.membersof.len;
String foo_name = Foo.nameof;

```c3
usz foo_alignment = Foo.alignof;
usz foo_member_count = Foo.membersof.len;
String foo_name = Foo.nameof;
```

To read more about all the fields available at compile time, see the page on [reflection](../reflection).

0 comments on commit b0ae6ee

Please sign in to comment.