title: What Is C3? description: A guide to the C3 Programming Language sidebar:
order: 1
:::note[Want To Download C3?] Download C3, available on Mac, Windows and Linux. :::
C3 Programming Language
C3 is an evolution of C and a minimalist systems programming language.
🦺 Ergonomics and Safety
- Optionals to safely and quickly handle errors and null.
- Defer to clean up resources.
- Slices and foreach for safe iteration.
- Contracts in comments, to add constraints to your code.
⚡ Performance by default
- Write SIMD vectors to program the hardware directly.
- Access to different memory allocators to fine tune performance.
- Zero overhead errors.
- Fast compilation times.
- LLVM backend for industrial strength optimisations.
- Easy to use inline assembly.
🔋Batteries included standard library
- Dynamic containers and strings.
- Cross-platform abstractions for ease of use.
- Access to the native platform when you need it.
🔧 Leverage existing C or C++ libraries
- Full C ABI compatibility.
- C3 can link C code, C can link C3 code.
📦 Modules are simple
- Modules namespace code.
- Modules make encapsulation simple with explicit control.
- Interfaces define shared behaviour to write robust libraries.
- Generic modules make extending code easier.
- Simple struct composition and reuse with struct subtyping.
🎓 Macros without a PhD
- Macros can be similar to normal functions.
- Or write code that understands the types in your code.
title: Design Goals & Background description: Design Goals & Background sidebar:
order: 2
:::note[Want To Download C3?] Download C3, available on Mac, Windows and Linux. :::
Design goals
- Procedural language, with a pragmatic ethos to get work done.
- Minimalistic, no feature should be unnecessary or redundant.
- Stay close to C - only change where there is a significant need.
- Learning C3 should be easy for a C programmer.
- Seamless C integration.
- Ergonomic common patterns.
- Data is inert.
- Zero Is Initialization (ZII).*
- Avoid “big ideas”.
“Zero Is Initialization” is an idiom where types and code are written so that the zero value is a meaningful, initialized state.*
Features
- Full C ABI compatibility
- Module system
- Generic modules
- Design by contract
- Zero overhead errors
- Semantic macro system
- First-class SIMD vector types
- Struct subtyping
- Safe array access using slices
- Safe array iteration using foreach
- Easy to use inline assembly
- Cross-platform standard library which includes dynamic containers and strings
- LLVM backend
C3 Background
C3 is an evolution of C, a minimalistic language designed for systems programming, enabling the same paradigms and retaining the same syntax as far as possible.
C3 started as an experimental fork of the C2 language by Bas van den Berg. It has evolved significantly, not just in syntax but also in regard to error handling, macros, generics and strings.
title: Roadmap For C3 description: Roadmap For C3 sidebar:
order: 3
:::note[Want To Download C3?] Download C3, available on Mac, Windows and Linux. :::
C3 Roadmap
C3 Is Feature Stable
The C3 0.6.x
series can be run in production with the same general caveats for using any pre-1.0
software.
While we strive to have zero bug count, there are still bugs being found. This means that anyone using it in production would need to stay updated with the latest fixes.
The focus of 0.7
–0.9
will be fleshing out the cross-platform standard
library and make sure the syntax and semantics are solid. Also, the
toolchain will expand and improve. Please refer to this issue for what’s
left in terms of features for 1.0
.
The intended roadmap has one major 0.1
release per year:
| Date | Release |
|——————|—————-|
| 2025-04-01 | 0.7 |
| 2026-04-01 | 0.8 |
| 2027-04-01 | 0.9 |
| 2028-04-01 | 1.0 |
Compatibility
Minor releases in the same major release series are compatible.
For example 0.6.0
, 0.6.1
, … 0.6.x
are compatible and 0.7.0
, 0.7.1
, … 0.7.x
are compatible.
Standard library
The standard library is less mature than the compiler. It needs more
functionality and more tests. The compiler reaching a 1.0
release only
means a language freeze, the standard library will continue to evolve
past the 1.0
release.
title: Install C3 Compiler Binary description: Installing C3 Compiler Binary sidebar:
order: 20
Prebuilt binaries
- Installing on Windows
- Installing on Mac Arm64
- Installing on Ubuntu
- Installing on Debian
- Installing on Arch
Installing on Windows
- Download the C3 compiler. Or the debug build
- Unzip it into a folder
- Either Visual Studio 17 or follow the next two steps.
- Run the
msvc_build_libraries.py
Python script which will download the necessary files to compile on Windows.
:::note[Running the Python script]
If you don’t have Python installed, you can download it from the Python Website. or get it from the the Microsoft Store
Afterwards you can double click the msvc_build_libraries.py
file and pick “python” from the list of programs in the “Select an app to open this .py file” window.
:::
Optional: set c3c as a global environment variable
- copy the folder
- navigate to
C:\Program Files
- paste the folder here
- navigate inside the folder you’ve pasted
- copy the path of the folder
- search for “edit the system environment variables” on your computer
- click on the “environment variables” button on the bottom right
- under “user variables” double click on “path”
- click on “new” and paste the path to the folder
- run
c3c
on anywhere in your computer!c3c compile ./hello.c3
Installing on Mac Arm64
- Make sure you have XCode with command line tools installed.
- Download the zip file (debug version here)
- Unzip executable and standard lib.
- Run
./c3c
.
Installing on Ubuntu
- Download tar file (debug version here)
- Unpack executable and standard lib.
- Run
./c3c
.
Installing on Debian
- Download tar file (debug version here)
- Unpack executable and standard lib.
- Run
./c3c
.
Installing on Arch Linux
There is an AUR package for the c3c compiler : c3c-git.
Due to some issues with the LLVM packaged for Arch Linux, the AUR package will download and use LLVM 16 from Ubuntu-23.04 to compile the c3c compiler.
You can use your AUR package manager:
paru -S c3c-git
# or yay -S c3c-git
# or aura -A c3c-git
Or clone it manually:
git clone https://aur.archlinux.org/c3c-git.git
cd c3c-git
makepkg -si
title: Build C3 From Source description: Build C3 From Source sidebar:
order: 21
:::note[Want To Download Pre-Built C3 Binaries?] Download C3, available on Mac, Windows and Linux. :::
For other platforms it should be possible to compile it on any platform LLVM can compile to. You will need git
and CMake
installed.
1. Install LLVM
See LLVM the LLVM documentation on how to set up LLVM for development. - On MacOS, installing through Homebrew or MacPorts works fine. - Using apt-get on Linux should work fine as well. - For Windows you can download suitable pre-compiled LLVM binaries from https://github.com/c3lang/win-llvm
2. Clone the C3 compiler source code from Github
This should be as simple as doing:
git clone https://github.com/c3lang/c3c.git
… from the command line.
3. Build the compiler
Create the build directory:
MyMachine:c3c$ mkdir build
MyMachine:c3c$ cd build/
Use CMake to set up:
MyMachine:c3c/build$ cmake ../
Build the compiler:
MyMachine:c3c/build$ make
4. Test it out
MyMachine:c3c/build$ ./c3c compile-run ../resources/testfragments/helloworld.c3
Building via Docker
You can build c3c
using either an Ubuntu 18.04 or 20.04 container:
./build-with-docker.sh 18
Replace 18
with 20
to build through Ubuntu 20.04.
For a release build specify:
./build-with-docker.sh 20 Release
A c3c
executable will be found under bin/
.
Building on Mac using Homebrew
- Install CMake:
brew install cmake
- Install LLVM 17+:
brew install llvm
- Clone the C3C github repository:
git clone https://github.com/c3lang/c3c.git
- Enter the C3C directory
cd c3c
. - Create a build directory
mkdir build
- Change directory to the build directory
cd build
- Set up CMake build for debug:
cmake ..
- Build:
cmake --build .
Building on Mac using MacPorts
c3c
may be built on Mac systems not supported by Homebrew
using the cmake, llvm-17 and clang-17
ports from MacPorts.
- Install CMake:
sudo port install cmake
- Install LLVM 17:
sudo port install llvm-17
- Install clang 17:
sudo port install clang-17
- Clone the C3C github repository:
git clone https://github.com/c3lang/c3c.git
- Enter the C3C directory
cd c3c
. - Create a build directory
mkdir build
- Change directory to the build directory
cd build
- ❗️Important before you run cmake❗️
Set LLVM_DIR to the directory with the llvm-17 macport .cmake files
export LLVM_DIR=/opt/local/libexec/llvm-17/lib/cmake/llvm
- Set up CMake build for debug:
cmake ..
- Build:
cmake --build .
See also discussion #1701
title: Hello World description: Learn to write hello world sidebar:
order: 30
:::note[Not installed the C3 compiler yet?] Download C3, available on Mac, Windows and Linux. :::
👋 Hello world
Let’s start with the traditional first program, Hello World in C3:
import std::io;
fn void main()
{
io::printn("Hello, World!");
}
The import
statement imports other modules, and we want printn
which
is in std::io
.
Next we define a function which starts with the fn
keyword followed by the return type. We don’t need to return anything, so return void
. The function name main
then follows, followed by the function’s parameter list, which is empty.
fn void main() {}
:::note
The function named main
is a bit special, as it is where the program starts, or the entry point of the program.
For Unix-like OSes there are a few different variants, for example we might declare it as fn void main(String[] args)
. In that case the parameter “args” contains a slice of strings, of the program’s command line arguments, starting with the name of the program, itself.
:::
🔭 Function scope
{
and }
signifies the start and end of the function respectively,
we call this the function’s scope. Inside the function scope we have a single function
call to printn
inside std::io
. We use the last part of the path “io” in front of
the function to identify what module it belongs to.
📏 Imports can use a shorthand
We could have used the original longer path: std::io::printn
if we wanted, but we can shorten it to just the lowest level module like io::printn
. This is the convention in C3 and is is known as “path-shortening”, it avoids writing long import paths that can make code harder to read.
```diff lang=”cpp” - std::io::printn(“Hello, World!”); + io::printn(“Hello, World!”);
The `io::printn` function takes a single argument and prints it, followed by a newline, then the function ends and the program terminates.
## 🔧 Compiling the program
Let's take the above program and put it in a file called `hello_world.c3`.
We can then compile it with:
```bash
c3c compile hello_world.c3
And run it:
./hello_world
It should print Hello, World!
and return back to the command line prompt.
If you are on Windows, you will have hello_world.exe
instead. Call it in the same way.
🏃 Compiling and running
When we start out it can be useful to compile and then have the compiler start the
program immediately. We can do that with compile-run
:
```bash {4} $ c3c compile-run hello_world.c3
Program linked to executable ‘hello_world’. Launching hello_world… Hello, World ```
Want more options when compiling? Check the c3c compiler build options.
🎉 Successfully working?
Congratulations! You’re now up and running with C3.
❓ Need help?
We’re happy to help on the C3 Discord.
title: Projects description: Learn to create C3 projects sidebar:
order: 31
import { FileTree } from ‘@astrojs/starlight/components’;
:::note[Not installed the C3 compiler yet?] Download C3, available on Mac, Windows and Linux. :::
Projects in C3
Projects are optional, but are a good way to manage compiling code when there are a lot of files and modules. They also allow you to specify libraries to link, and define how your project should be built for specific targets.
💡 Creating a new project
The c3c init
command will create a new directory containing your project structure.
It requires a name of the project, we will use myc3project
in its place.
c3c init myc3project
You can also customize the path where the project will be created or specify a template. For more information check the init command reference.
📁 Project structure
If you check the directory that was created you might find it a bit confusing with a bunch of different directories, but worry not because if you expand them you will realise that most of them are actually empty!
Directory Overview
Directory | Usage |
---|---|
./build |
Where your temporary files and build results will go. |
./docs |
Code Documentation |
./lib |
C3 libraries (with the .c3l suffix) |
./resources |
Non-code resources like images, sound effects etc. |
./scripts |
Scripts, including .c3 scripts that generate code at compile time. |
./src |
Storing our code, by default contains main.c3 with “Hello World”. |
project.json |
Record project information, similar to package.json in NodeJS. |
LICENSE |
Project license. |
README.md |
Help others understand and use your code. |
🔧 Building the project
Assuming you have successfully initialized a project as seen above, we can now look at how to compile it.
🏃 Build & run
C3 has a simple command to build & run our project.
```bash {4} c3c run
Program linked to executable ‘build/myc3project’. Launching ./build/myc3project… Hello, World ```
You can also specify the target to build & run.
c3c run myc3project
🔧 Build
If you only want to build the project, you can use the build
command:
c3c build
This command builds the project targets defined in our project.json
file.
:::note
If you want to build a specific target, you can do so by specifying its name.
The default target is created with the name of the project, such as myc3project
.
c3c build myc3project
:::
We will now have a binary in build
, which we can run:
./build/myc3project
It should print Hello, World!
and return back to the command line prompt.
If you are on Windows, you will have myc3project.exe
instead. Call it in the same way.
If you need more detail later on check C3 project build commands and C3 project configuration to learn more.
title: Community & Contribute description: Info regarding the development of C3 sidebar:
order: 10
Contributions Welcome!
The C3 language is still in its development phase, which means functionality and specification are subject to change. That also means that any contribution right now will have a big impact on the language. So if you find the project interesting, here’s what you can do to help:
💬 Discuss The Language
- Join us on C3 Discord.
- Open a thread on Discourse.
💡 Suggest Improvements
- Found a bug? File an issue for C3 compiler
- Spotted a typo or broken link? File an issue for the website
💪 Contribute
Now the compiler is stable, what is needed now are the non-essentials, such as a docs generator, editor plugins, LSP etc.
title: Basic Types and Values description: Get an overview of C3’s basic types and values sidebar:
order: 33
C3 provides a similar set of fundamental data types as C: integers, floats, arrays and pointers. On top of this it
expands on this set by adding slices and vectors, as well as the any
and typeid
types for advanced use.
Integers
C3 has signed and unsigned integer types. The built-in signed integer types are ichar
, short
, int
, long
,
int128
, iptr
and isz
. ichar
to int128
have all well-defined power-of-two bit sizes, whereas iptr
has the same number of bits as a void*
and isz
has the same number of bits as the maximum difference
between two pointers. For each signed integer type there is a corresponding unsigned integer type: char
,
ushort
, uint
, ulong
, uint128
, uptr
and usz
.
type | signed? | min | max | bits |
---|---|---|---|---|
ichar | yes | -128 | 127 | 8 |
short | yes | -32768 | 32767 | 16 |
int | yes | -2^31 | 2^31 - 1 | 32 |
long | yes | -2^63 | 2^63 - 1 | 64 |
int128 | yes | -2^127 | 2^127 - 1 | 128 |
iptr | yes | varies | varies | varies |
isz | yes | varies | varies | varies |
char | no | 0 | 255 | 8 |
ushort | no | 0 | 65535 | 16 |
uint | no | 0 | 2^32 - 1 | 32 |
ulong | no | 0 | 2^64 - 1 | 64 |
uint128 | no | 0 | 2^128 - 1 | 128 |
uptr | no | 0 | varies | varies |
usz | no | 0 | varies | varies |
On 64-bit machines iptr
/uptr
and isz
/usz
are usually 64-bits, like long
/ulong
.
On 32-bit machines on the other hand they are generally int
/uint
.
Integer constants
Numeric constants typically use decimal, e.g. 234
, but may also use hexadecimal (base 16) numbers by prefixing
the number with 0x
or 0X
, e.g. int a = 0x42edaa02;
. There is also octal (base 8) using the
0o
or 0O
prefix, and 0b
for binary (base 2) numbers:
Numbers may also insert underscore _
between digits to improve readability, e.g. 1_000_000
.
a = -2_000;
b = 0o770;
c = 0x7f7f7f;
For decimal numbers, the value is assumed to be a signed int
, unless the number doesn’t fit in an
int
, in which case it is assumed to be the smallest signed type it does fit in (long
or int128
).
For hexadecimal, octal and binary, the type is assumed to be unsigned.
A integer literal can implicitly convert to a floating point literal, or an integer of a different type provided the number fits in the type.
Constant suffixes
If you want to ensure that a constant is of a certain type, you can either add an explicit cast
like: (ushort)345
, or use an integer suffix: 345u16
.
The following integer suffixes are available:
suffix | type |
---|---|
i8 | ichar |
i16 | short |
i32 | int |
i64 | long |
i128 | int128 |
u8 | char |
u16 | ushort |
u32 | uint |
u | uint |
u64 | ulong |
u128 | uint128 |
Note how uint
also has the u
suffix.
Booleans
A bool
will be either true
or false
. Although a bool is only a single bit of data,
it should be noted that it is stored in a byte.
bool b = true;
bool f = false;
Character literals
A character literal is a value enclosed in ''
. Its value is interpreted as being its
ASCII value for a single character.
It is also possible to use 2, 4 or 8 character wide character literals. Such are interpreted
as ushort
, uint
and ulong
respectively and are laid out in memory from left to right.
This means that the actual value depends on the endianness
of the target.
- 2 character literals, e.g.
'C3'
, would convert to an ushort. - 4 character literals, e.g.
'TEST'
, converts to an uint. - 8 character literals, e.g.
'FOOBAR11'
converts to an ulong.
The 4 character literals correspond to the layout of FourCC
codes. It will also correctly arrange unicode characters in memory. E.g. Char32 smiley = '\u1F603'
Floating point types
As is common, C3 has two floating point types: float
and double
. float
is the 32 bit floating
point type and double
is 64 bits.
Floating point constants
Floating point constants will at least use 64 bit precision.
Just like for integer constants, it is possible to use _
to improve
readability, but it may not occur immediately before or after a dot or an exponential.
C3 supports floating points values either written in decimal or hexadecimal formats.
For decimal, the exponential symbol is e (or E, both are acceptable),
for hexadecimal p (or P) is used: -2.22e-21
-0x21.93p-10
While floating point numbers default to double
it is possible to type a
floating point by adding a suffix:
Suffix | type |
---|---|
f32 or f | float |
f64 | double |
Arrays
Arrays have the format Type[size]
, so for example: int[4]
. An array is a type consisting
of the same element repeated a number of times. Our int[4]
is essentially four int
values
packed together.
For initialization it’s sometimes convenient to use the wildcard Type[*]
declaration, which
infers the length from the number of elements:
int[3] abc = { 1, 2, 3 }; // Explicit int[3]
int[*] bcd = { 1, 2, 3 }; // Implicit int[3]
Slices
Slices have the format Type[]
. Unlike the array, a slice does not hold the values themselves
but instead presents a view of some underlying array or vector.
Slices have two properties: .ptr
, which retrieves the array it points to, and .len
which
is the length of the slice - that is, the number of elements it is possible to index into.
Usually we can get a slice by taking the address of an array:
int[3] abc = { 1, 2, 3 };
int[] slice = &abc; // A slice pointing to abc with length 3
Because indexing into slices is range checked in safe mode, slices are vastly more safe providing pointer + length separately.
Vectors
Vectors similar to arrays, use the format
Type[<size>]
, with the restriction that vectors may only form out
of integers, floats and booleans. Similar to arrays, wildcard can be
used to infer the size of a vector:
int[<*>] a = { 1, 2 };
Vectors are based on hardware SIMD vectors, and supports many different operations that work on all elements in parallel, including arithmetics:
int[<2>] b = { 3, 8 };
int[<2>] c = { 7, 2 };
int[<2>] d = b * c; // d is { 21, 16 }
Vector initialization and literals work the same way as arrays, using { ... }
String literals
String literals are special and can convert to several different types:
String
, char
and ichar
arrays and slices and finally ichar*
and char*
.
String literals are text enclosed in " "
just like in C. These support
escape sequences like \n
for line break and need to use \"
for any "
inside of the
string.
C3 also offers raw strings which are enclosed in ` `
.
A raw string may span multiple lines.
Inside of a raw string, no escapes are available, and to write a `
, simply double the character:
// Note: String is a distinct inline char[]
String three_lines =
`multi
line
string`;
String foo = `C:\foo\bar.dll`;
String bar = `"Say ``hello``"`;
// Same as
String foo = "C:\\foo\\bar.dll";
String bar = "\"Say `hello`\"";
String
is a
distinct inline char[]
, which can implicitly convert to char[]
when required.
ZString
is a distinct inline char*
.ZString
is a C compatible null terminated string, which can implicitly convert to char*
when required.
Base64 and hex data literals
Base64 literals are strings prefixed with b64
to containing
Base64 encoded data, which
is converted into a char array at compile time:
// The array below contains the characters "Hello World!"
char[*] hello_world_base64 = b64"SGVsbG8gV29ybGQh";
The corresponding hex data literals convert a hexadecimal string rather than Base64:
// The array below contains the characters "Hello World!"
char[*] hello_world_hex = x"4865 6c6c 6f20 776f 726c 6421";
Pointer types
Pointers have the syntax Type*
. A pointer is a memory address where one or possibly more
elements of the underlying address are stored. Pointers can be stacked: Foo*
is a pointer to a Foo
while Foo**
is a pointer to a pointer to Foo
.
The pointer type has a special literal called null
, which is an invalid, empty pointer.
void*
The void*
type is a special pointer which implicitly converts to any other pointer. It is not “a pointer to void”,
but rather a wildcard pointer which matches any other pointer.
Printing values
Printing values can be done using io::print
, io::printn
, io::printf
and io::printfn
. This requires
importing the module std::io
.
:::note
The n
variants of the print functions will add a newline after printing, which is what we’ll often
use in the examples, but print
and printf
work the same way.
:::
import std::io; // Get the io functions.
fn void main()
{
int a = 1234;
ulong b = 0xFFAABBCCDDEEFF;
double d = 13.03e-04;
char[*] hex = x"4865 6c6c 6f20 776f 726c 6421";
io::printn(a);
io::printn(b);
io::printn(d);
io::printn(hex);
}
If you run this program you will get:
1234
71963842633920255
0.001303
[72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33]
To get more control we can format the output using printf
and printfn
:
import std::io;
fn void main()
{
int a = 1234;
ulong b = 0xFFAABBCCDDEEFF;
double d = 13.03e-04;
char[*] hex = x"4865 6c6c 6f20 776f 726c 6421";
io::printfn("a was: %d", a);
io::printfn("b in hex was: %x", b);
io::printfn("d in scientific notation was: %e", d);
io::printfn("Bytes as string: %s", (String)&hex);
}
We can apply the standard printf formatting rules, but
unlike in C/C++ there is no need to indicate the type when using %d
- it will print unsigned and
signed up to int128
, in fact there is no support for %u
, %lld
etc in io::printf
. Furthermore,
%s
works not just on strings but on any type:
import std::io;
enum Foo
{
ABC,
BCD,
EFG,
}
fn void main()
{
int a = 1234;
uint128 b = 0xFFEEDDCC_BBAA9988_77665544_33221100;
Foo foo = BCD;
io::printfn("a: %s, b: %d, foo: %s", a, b, foo);
}
This prints:
a: 1234, b: 340193404210632335760508365704335069440, foo: BCD
title: Comments & Documentation description: Comments & Documentation sidebar:
order: 40
C3 uses three distinct comment types:
- The normal
//
single line comment. - The classic
/* ... */
multi-line C style comment, but unlike in C they are allowed to nest. - Documentation comments
<* ... *>
the text within these comments will be parsed as documentation and optional Contracts on the following code.
Doc contracts
Documentation contracts start with <*
and must be terminated using *>
.
Any initial text up until the first @
-directive on a new line will be interpreted as
free text documentation.
For example:
<*
Here are some docs.
@param num_foo `The number of foos.`
@require num_foo > 4
@deprecated
@mycustom 2
*>
void bar(int num_foo)
{
io::printfn("%d", num_foo);
}
Doc Contracts Are Parsed
The following was extracted:
- The function description: “Here are some docs.”
- The num_foo
parameter has the description: “The number of foos”.
- A Contract annotation for the compiler: @require num_foo > 4
which tells the compiler and a user of the function that a precondition is that num_foo
must be greater than 4.
- A function Attribute marking it as @deprecated
, which displays warnings.
- A custom function Attribute @mycustom
.
The compiler is free to silently ignore custom Attributes, they can be
used to optionally emit warnings, but are otherwise ignored.
Available annotations
Name | format |
---|---|
@param | @param <param> <description> |
@return | @return <description> |
@return! | @return! <fault1>, <fault2>, ... |
@deprecated | @deprecated <optional description> |
@require | @require <expr1>, <expr2>, ... |
@ensure | @ensure <expre1>, <expr2>, ... |
@pure | @pure |
See Contracts for information regarding @require
, @ensure
, @const
, @pure
, @checked
.
title: Naming Rules description: Naming Rules sidebar:
order: 41
C3 introduces fairly rigid naming rules to reduce ambiguity and make the language easy to parse for tools.
As a basic rule, all identifiers are limited to a-z, A-Z, 0-9 and _
. The initial character can not be a number. Furthermore, all identifiers are limited to 31 character.
Structs, unions, enums and faults
All user defined types must start with A-Z after any optional initial _
and include at least 1 lower case letter. Bar
, _T_i12
and TTi
are all valid names. _1
, bAR
and BAR
are not. For C-compatibility it’s possible to alias the type to a external name using the attribute “extern”.
struct Foo @extern("foo")
{
int x;
Bar bar;
}
union Bar
{
int i;
double d;
}
enum Baz
{
VALUE_1,
VALUE_2
}
fault Err
{
OOPS,
LOTS_OF_OOPS
}
Variables and parameters
All variables and parameters except for global constant variables must start with a-z after any optional initial _
. ___a
fooBar
and _test_
are all valid variable / parameter names. _
, _Bar
, X
are not.
int theGlobal = 1;
fn void foo(int x)
{
Foo foo = getFoo(x);
theGlobal++;
}
Global constants
Global constants must start with A-Z after any optional initial _
. _FOO2
, BAR_FOO
, X
are all valid global constants, _
, _bar
, x
are not.
const int A_VALUE = 12;
Enum / Fault values
Enum and fault values follow the same naming standard as global constants.
enum Baz
{
VALUE_1,
VALUE_2
}
fault Err
{
OOPS,
LOTS_OF_OOPS
}
Struct / union members
Struct and union members follow the same naming rules as variables.
Modules
Module names may contain a-z, 0-9 and _
, no upper case characters are allowed.
module foo;
Functions and macros
Functions and macros must start with a-z after any optional initial _
.
fn void theMostAmazingFunction()
{
return;
}
macro justDoIt(x)
{
justDo(x);
}
title: Variables description: Variables sidebar:
order: 41
Zero init by default
Unlike C, C3 local variables are zero-initialized by default. To avoid zero initialization, you need to explicitly opt-out.
int x; // x = 0
int y @noinit; // y is explicitly undefined and must be assigned before use.
AStruct foo; // foo is implicitly zeroed
AStruct bar = {}; // bar is explicitly zeroed
AStruct baz @noinit; // baz is explicitly undefined
Using a variable that is explicitly undefined before will trap or be initialized to a specific value when compiling “safe” and is undefined behaviour in “fast” builds.
title: Expressions description: Expressions sidebar:
order: 42
Expressions work like in C, with one exception: it is possible to take the address of a temporary. This uses the operator &&
rather than &
.
Consequently, this is valid:
fn void test(int* x) { ... }
test(&&1);
// In C:
// int x = 1;
// test(&x);
Well-defined evaluation order
Expressions have a well-defined evaluation order:
- Binary expressions are evaluated from left to right.
- Assignment occurs right to left, so
a = a++
would result ina
being unchanged. - Call arguments are evaluated in parameter order.
Compound literals
C3 has C’s compound literals, but unlike C’s cast style syntax (MyStruct) { 1, 2 }
,
it uses C++ syntax: MyStruct { 1, 2 }
.
struct Foo
{
int a;
double b;
}
fn void test1(Foo x) { ... }
...
test1(Foo { 1, 2.0 });
Arrays follow the same syntax:
fn void test2(int[3] x) { ... }
...
test2(int[3] { 1, 2, 3 });
Note that when it’s possible, inferring the type is allowed, so we have for the above examples:
test1({ 1, 2.0 });
test2({ 1, 2, 3 });
One may take the address of temporaries, using &&
(rather than &
for normal variables). This allows the following:
Passing a slice
fn void test(int[] y) { ... }
// Using &&
test(&&int[3]{ 1, 2, 3 });
// Explicitly slicing:
test(int[3]{ 1, 2, 3 }[..]);
// Using a slice directly as a temporary:
test(int[]{ 1, 2, 3 });
// Same as above but with inferred type:
test({ 1, 2, 3 });
Passing the pointer to an array
fn void test1(int[3]* z) { ... }
fn void test2(int* z) { ... }
test1(&&int[3]{ 1, 2, 3 });
test2(&&int[3]{ 1, 2, 3 });
Constant expressions
In C3 all constant expressions are guaranteed to be calculated at compile time. The following are considered constant expressions:
- The
null
literal. - Boolean, floating point and integer literals.
- The result of arithmetics on constant expressions.
- Compile time variables (prefixed with
$
) - Global constant variables with initializers that are constant expressions.
- The result of macros that does not generate code and only uses constant expressions.
- The result of a cast if the value is cast to a boolean, floating point or integer type and the value that is converted is a constant expression.
- String literals.
- Initializer lists containing constant values.
Some things that are not constant expressions:
- Any pointer that isn’t the
null
literal, even if it’s derived from a constant expression. - The result of a cast except for casts of constant expressions to a numeric type.
- Compound literals - even when values are constant expressions.
Including binary data
The $embed(...)
function includes the contents of a file into the compilation as a
constant array of bytes:
char[*] my_image = $embed("my_image.png");
The result of an embed work similar to a string literal and can implicitly convert to a char*
,
void*
, char[]
, char[*]
and String
.
Limiting length
It’s possible to limit the length of included with the optional second parameter.
char[4] my_data = $embed("foo.txt", 4);
Failure to load at compile time and defaults
Usually it’s a compile time error if the file can’t be included, but sometimes it’s useful to only optionally include it. If this is desired, declare the left hand side an Optional:
char[]! my_image = $embed("my_image.png");
my_image
with be an optional IoError.FILE_NOT_FOUND?
if the image is missing.
This also allows us to pass a default value using ??
:
char[] my_image = $embed("my_image.png") ?? DEFAULT_IMAGE_DATA;
title: Statements description: Statements sidebar:
order: 43
Statements largely work like in C, but with some additions.
Expression blocks
Expression blocks (delimited using {| |}
) are compound statements that opens their own function scope.
Jumps cannot be done into or out of a function block, and return
exits the block, rather than the function as a whole.
The function below prints World!
fn void test()
{
int a = 0;
{|
if (!a) return;
io::printf("Hello ");
return;
|};
io::printf("World!\n");
}
Expression blocks may also return values:
fn void test(int x)
{
int a = {|
if (x > 0) return x * 2;
if (x == 0) return 100;
return -x;
|};
io::printfn("The result was %d", a);
}
Labelled break and continue
Labelled break
and continue
lets you break out of an outer scope. Labels can be put on if
,
switch
, while
and do
statements.
fn void test(int i)
{
if FOO: (i > 0)
{
while (1)
{
io::printfn("%d", i);
// Break out of the top if statement.
if (i++ > 10) break FOO;
}
}
}
Do-without-while
Do-while statements can skip the ending while
. In that case it acts as if the while
was while(0)
:
do
{
io::printn("FOO");
} while (0);
// Equivalent to the above.
do
{
io::printn("FOO");
};
Nextcase and labelled nextcase
The nextcase
statement is used in switch
and if-catch
to jump to the next statement:
switch (i)
{
case 1:
doSomething();
nextcase; // Jumps to case 2
case 2:
doSomethingElse();
}
It’s also possible to use nextcase
with an expression, to jump to an arbitrary case:
switch (i)
{
case 1:
doSomething();
nextcase 3; // Jump to case 3
case 2:
doSomethingElse();
case 3:
nextcase rand(); // Jump to random case
default:
io::printn("Ended");
}
Which can be used as structured goto
when creating state machines.
Switch cases with runtime evaluation
It’s possible to use switch
as an enhanced if-else chain:
switch (true)
{
case x < 0:
xless();
case x > 0:
xgreater();
default:
xequals();
}
The above would be equivalent to writing:
if (x < 0)
{
xless();
}
else if (x > 0)
{
xgreater();
}
else
{
xequals();
}
Note that because of this, the first match is always picked. Consider:
switch (true)
{
case x > 0:
foo();
case x > 2:
bar();
}
Because of the evaluation order, only foo()
will be invoked for x > 0, even when x is greater than 2.
It’s also possible to omit the conditional after switch
. In that case it is implicitly assumed to be same as
writing (true)
switch
{
case foo() > 0:
bar();
case test() == 1:
baz();
}
title: Functions description: Functions sidebar:
order: 45
C3 has both regular functions and member functions. Member functions are functions namespaced using type names, and allows invocations using the dot syntax.
Regular functions
Regular functions are the same as C aside from the keyword fn
, which is followed by the conventional C declaration of <return type> <name>(<parameter list>)
.
fn void test(int times)
{
for (int i = 0; i < times; i++)
{
io::printfn("Hello %d", i);
}
}
Function arguments
C3 allows use of default arguments as well as named arguments. Note that any unnamed arguments must appear before any named arguments.
fn int test_with_default(int foo = 1)
{
return foo;
}
fn void test()
{
test_with_default();
test_with_default(100);
}
Named arguments
fn void test_named(int times, double data)
{
for (int i = 0; i < times; i++)
{
io::printf("Hello %d\n", i + data);
}
}
fn void test()
{
// Named only
test_named(times: 1, data: 3.0);
// Unnamed only
test_named(3, 4.0);
// Mixing named and unnamed
test_named(15, data: 3.141592);
}
Named arguments with defaults:
fn void test_named_default(int times = 1, double data = 3.0, bool dummy = false)
{
for (int i = 0; i < times; i++)
{
io::printfn("Hello %f", i + data);
}
}
fn void test()
{
// Named only
test_named_default(times: 10, data: 3.5);
// Unnamed and named
test_named_default(3, dummy: false);
// Overwriting an unnamed argument with a named argument is an error:
// test_named_default(2, times: 3); ERROR!
// Unnamed may not follow named arguments.
// test_named_default(times: 3, 4.0); ERROR!
}
Varargs
There are four types of varargs:
- single typed
- explicitly typed any: pass non-any arguments as references
- implicitly typed any: arguments are implicitly converted to references (use with care)
- untyped C-style
Examples:
fn void va_singletyped(int... args)
{
/* args has type int[] */
}
fn void va_variants_explicit(any*... args)
{
/* args has type any*[] */
}
fn void va_variants_implicit(args...)
{
/* args has type any*[] */
}
extern fn void va_untyped(...); // only used for extern C functions
fn void test()
{
va_singletyped(1, 2, 3);
int x = 1;
any* v = &x;
va_variants_explicit(&&1, &x, v); // pass references for non-any arguments
va_variants_implicit(1, x, "foo"); // arguments are implicitly converted to anys
va_untyped(1, x, "foo"); // extern C-function
}
For typed varargs, we can pass a slice instead of the individual arguments, by using the splat ...
operator for example:
fn void test_splat()
{
int[] x = { 1, 2, 3 };
va_singletyped(...x);
}
Splat
- Splat
...
unknown size slice ONLY in a typed vaarg slot.
fn void va_singletyped(int... args) {
io::printfn("%s", args);
}
fn void main()
{
int[2] arr = {1, 2};
va_singletyped(...arr); // arr is splatting two arguments
}
- Splat
...
any array anywhere
fn void foo(int a, int b, int c)
{
io::printfn("%s, %s, %s", a, b, c);
}
fn void main()
{
int[2] arr = {1, 2};
foo(...arr, 7); // arr is splatting two arguments
}
- Splat
...
known size slices anywhere
fn void foo(int a, int b, int c)
{
io::printfn("%s, %s, %s", a, b, c);
}
fn void main()
{
int[5] arr = {1, 2, 3, 4, 5};
foo(...arr[:3]); // slice is splatting three arguments
}
Named arguments and varargs
Usually, a parameter after varargs would never be assigned to:
fn void testme(int a, double... x, double rate = 1.0) { /* ... */ }
fn void test()
{
// x is { 2.0, 5.0, 6.0 } rate would be 1.0
testme(3, 2.0, 5.0, 6.0);
}
However, named arguments can be used to set this value:
fn void testme(int a, double... x, double rate = 1.0) { /* ... */ }
fn void test()
{
// x is { 2.0, 5.0 } rate would be 6.0
testme(3, 2.0, 5.0, rate: 6.0);
}
Functions and Optional returns
Function return values may be Optionals – denoted by <type>!
indicating that this
function might either return an Optional with a result, or an Optional with an Excuse.
For example this function might return an Excuse of type SomeError
or OtherResult
.
fn double! test_error()
{
double val = random_value();
if (val >= 0.2) return SomeError.BAD_JOSS_ERROR?;
if (val > 0.5) return OtherError.BAD_LUCK_ERROR?;
return val;
}
A function call which is passed one or more Optional arguments will only execute if all Optional values contain a result, otherwise the first Excuse found is returned.
fn void test()
{
// The following line is either prints a value less than 0.2
// or does not print at all:
io::printfn("%d", test_error());
// ?? sets a default value if an Excuse is found
double x = (test_error() + test_error()) ?? 100;
// This prints either a value less than 0.4 or 100:
io::printfn("%d", x);
}
This allows us to chain functions:
fn void print_input_with_explicit_checks()
{
String! line = io::readline();
if (try line)
{
// line is a regular "string" here.
int! val = line.to_int();
if (try val)
{
io::printfn("You typed the number %d", val);
return;
}
}
io::printn("You didn't type an integer :/ ");
}
fn void print_input_with_chaining()
{
if (try int val = io::readline().to_int())
{
io::printfn("You typed the number %d", val);
return;
}
io::printn("You didn't type an integer :/ ");
}
Methods
Methods look exactly like functions, but are prefixed with the type name and is (usually) invoked using dot syntax:
struct Point
{
int x;
int y;
}
fn void Point.add(Point* p, int x)
{
p.x += x;
}
fn void example()
{
Point p = { 1, 2 };
// with struct-functions
p.add(10);
// Also callable as:
Point.add(&p, 10);
}
The target object may be passed by value or by pointer:
enum State
{
STOPPED,
RUNNING
}
fn bool State.may_open(State state)
{
switch (state)
{
case STOPPED: return true;
case RUNNING: return false;
}
}
Implicit first parameters
Because the type of the first argument is known, it may be left out. To indicate a pointer &
is used.
fn int Foo.test(&self) { /* ... */ }
// equivalent to
fn int Foo.test(Foo* self) { /* ... */ }
fn int Bar.test(self) { /* ... */ }
// equivalent to
fn int Bar.test(Bar self) { /* ... */ }
It is customary to use self
as the name of the first parameter, but it is not required.
Restrictions on methods
- Methods on a struct/union may not have the same name as a member.
- Methods only works on distinct, struct, union and enum types.
- When taking a function pointer of a method, use the full name.
- Using subtypes, overlapping function names will be shadowed.
Contracts
C3’s error handling is not intended to use errors to signal invalid data or to check invariants and post conditions. Instead C3’s approach is to add annotations to the function, that conditionally will be compiled into asserts.
As an example, the following code:
<*
@param foo `the number of foos`
@require foo > 0, foo < 1000
@return `number of foos x 10`
@ensure return < 10000, return > 0
*>
fn int test_foo(int foo)
{
return foo * 10;
}
Will in debug builds be compiled into something like this:
fn int test_foo(int foo)
{
assert(foo > 0);
assert(foo < 1000);
int _return = foo * 10;
assert(_return < 10000);
assert(_return > 0);
return _return;
}
The compiler is allowed to use the contracts for optimizations. For example this:
fn int test_example(int bar)
{
// The following is always invalid due to the `@ensure`
if (test_foo(bar) == 0) return -1;
return 1;
}
May be optimized to:
fn int test_example(int bar)
{
return 1;
}
In this case the compiler can look at the post condition of result > 0
to determine that testFoo(foo) == 0
must always be false.
Looking closely at this code, we note that nothing guarantees that bar
is not violating the preconditions. In Safe builds this will usually be
checked in runtime, but a sufficiently smart compiler will warn about
the lack of checks on bar
. Execution of code violating pre and post conditions has unspecified behaviour.
Short function declaration syntax
For very short functions, C3 offers a “short declaration” syntax using =>
:
// Regular
fn int square(int x)
{
return x * x;
}
// Short
fn int square_short(int x) => x * x;
Lambdas
It’s possible to create anonymous functions using the regular fn
syntax. Anonymous
functions are identical to regular functions and do not capture variables from the
surrounding scope:
def IntTransform = fn int(int);
fn void apply(int[] arr, IntTransform t)
{
foreach (&i : arr) *i = t(*i);
}
fn void main()
{
int[] x = { 1, 2, 5 };
// Short syntax with inference:
apply(x, fn (i) => i * i);
// Regular syntax without inference:
// apply(x, fn int(int i) { return i * i; });
// Prints [1, 4, 25]
io::printfn("%s", x);
}
Static initializer and finalizers
It is sometimes useful to run code at startup and shutdown. Static initializers and finalizers
are regular functions annotated with @init
and @finalizer
that are run at startup and shutdown respectively:
fn void run_at_startup() @init
{
// Run at startup
some_function.init(512);
}
fn void run_at_shutdown() @finalizer
{
some_thing.shutdown();
}
Note that invoking @finalizer
is a best effort attempt by the OS and may not
be called during abnormal shutdown.
Changing priority of static initializers and finalizers
It is possible to provide an argument to the attributes to set the actual priority. It is recommended that programs use a priority of 1024 or higher. The higher the value, the later it will be called. The lowest priority is 65535.
// Print "Hello World" at startup.
fn void start_world() @init(3000)
{
io::printn("World");
}
fn void start_hello() @init(2000)
{
io::print("Hello ");
}
title: Modules description: Modules sidebar:
order: 46
C3 groups functions,
types, variables and macros into namespaces called modules. When doing
builds, any C3 file must start with the module
keyword,
specifying the module. When compiling single files, the module is not
needed and the module name is assumed to be the file name, converted to
lower case, with any invalid characters replaced by underscore (_
).
A module can consist of multiple files, e.g.
file_a.c3
module foo;
/* ... */
file_b.c3
module foo;
/* ... */
file_c.c3
module bar;
/* ... */
Here file_a.c3
and file_b.c3
belong to the same module, foo while file_c.c3
belongs to to bar.
Details
Some details about the C3 module system:
- Modules can be arbitrarily nested, e.g.
module foo::bar::baz;
to create the sub module baz in the sub modulebar
of the modulefoo
. - Module names must be alphanumeric lower case letters plus the underscore character:
_
. - Module names are limited to 31 characters.
- Modules may be spread across multiple files.
- A single file may have multiple module declarations.
- Each declaration of a distinct module is called a module section.
Importing Modules
Modules are imported using the import
statement. Imports always recursively import sub-modules. Any module
will automatically import all other modules with the same parent module.
foo.c3
module some::foo;
fn void test() {}
bar.c3
module bar;
import some;
// import some::foo; <- not needed, as it is a sub module to "some"
fn void test()
{
foo::test();
// some::foo::test() also works.
}
In some cases there may be ambiguities, in which case the full path can be used to resolve the ambiguity:
abc.c3
module abc;
struct Context
{
int a;
}
def.c3
module def;
struct Context
{
void* ptr;
}
test.c3
module test1;
import def, abc;
// Context c = {} <- ambiguous
abc::Context c = {};
Implicit Imports
The module system will also implicitly import:
- The
std::core
module (and sub modules). - Any other module sharing the same top module. E.g. the module
foo::abc
will implicitly also import modulesfoo
andfoo::cde
if they exist.
Visibility
All files in the same module share the same global declaration namespace. By default a symbol is visible to all other modules.
To make a symbol only visible inside the module, use the @private
attribute.
module foo;
fn void init() { .. }
fn void open() @private { .. }
In this example, the other
modules can use the init() function after importing foo, but only files
in the foo module can use open(), as it is specified as private
.
It’s possible to further restrict visibility: @local
works like @private
except it’s only visible in the
local context.
// File foo.c3
module foo;
fn void abc() @private { ... }
fn void def() @local { ... }
// File foo2.c3
module foo;
fn void test()
{
abc(); // Access of private in the same module is ok
// def(); <- Error: function is local to foo.c3
}
Overriding Symbol Visibility Rules
By using import <module> @public
, it’s possible to access another module´s private symbols.
Many other module systems have hierarchal visibility rules, but the import @public
feature allows
visibility to be manipulated in a more ad-hoc manner without imposing hard rules.
For example, you may provide a library with two modules: “mylib::net” and “mylib::file” - which both use functions
and types from a common “mylib::internals” module. The two libraries use import mylib::internals @public
to access this module’s private functions and type. To an external user of the library, the “mylib::internals”
does not seem to exist, but inside of your library you use it as a shared dependency.
A simple example:
// File a.c3
module a;
fn void a_function() @private { ... }
// File b.c3
module b;
fn void b_function() @private { ... }
// File c.c3
module c;
import a;
import b @public;
fn void test()
{
// Error! a_function() is private
a::a_function();
// Allowed since `import b @public` allowed `b`
// to "public" in this context.
b::b_function();
}
Note: @local
visibility cannot be overridden using a “@public” import.
Changing The Default Visibility
In a normal module, global declarations will be public by default. If some other
visibility is desired, it’s possible to declare @private
or @local
after the module name.
It will affect all declaration in the same section.
module foo @private;
fn void ab_private() { ... } // Private
module foo;
fn void ab_public() { ... } // Public
module bar;
import foo;
fn void test()
{
foo::ab_public(); // Works
// foo::ab_private(); <- Error, private method
}
If the default visibility is @private
or @local
, using @public
sets the visibility to public:
module foo @private;
fn void ab_private() { ... } // Private
fn void ab_public() @public { ... } // Public
Linker Visibility and Exports
A function or global prefixed extern
will be assumed to be linked in later.
An “extern” function may not have a body, and global variables are prohibited
from having an init expression.
The attribute @export
explicitly marks a function as being exported when
creating a (static or dynamic) library. It can also change the linker name of
the function.
Using Functions and Types From Other Modules
As a rule, functions, macros, constants, variables and types in the same module do not need any namespace prefix. For imported modules the following rules hold:
- Functions, macros, constants and variables require at least the (sub-) module name.
- Types do not require the module name unless the name is ambiguous.
- In case of ambiguity, only so many levels of module names are needed as to make the symbol unambiguous.
// File a.c3
module a;
struct Foo { ... }
struct Bar { ... }
struct TheAStruct { ... }
fn void anAFunction() { ... }
// File b.c3
module b;
struct Foo { ... }
struct Bar { ... }
struct TheBStruct { ... }
fn void aBFunction() { ... }
// File c.c3
module c;
import a, b;
struct TheCStruct { ... }
struct Bar { ... }
fn void aCFunction() { ... }
fn void test()
{
TheAStruct stA;
TheBStruct stB;
TheCStruct stC;
// Name required to avoid ambiguity;
b::Foo stBFoo;
// Will always pick the current module's
// name.
Bar bar;
// Namespace required:
a::aAFunction();
b::aBFunction();
// A local symbol does not require it:
aCFunction();
}
This means that the rule for the common case can be summarized as
Types are used without prefix; functions, variables, macros and constants are prefixed with the sub module name.
Module Sections
A single file may have multiple module declarations, even for the same module. This allows us to write for example:
// File foo.c3
module foo;
fn int hello_world()
{
return my_hello_world();
}
module foo @private;
import std::io; // The import is only visible in this section.
fn int my_hello_world() // @private by default
{
io::printn("Hello, world\n");
return 0;
}
module foo @test;
fn void test_hello() // @test by default
{
assert(hello_world() == 0);
}
Versioning and Dynamic Inclusion
NOTE: This feature may significantly change.
When including dynamic libraries, it is possible to use optional functions and globals. This is done using the
@dynamic
attribute.
An example library could have this:
dynlib.c3i
module dynlib;
fn void do_something() @dynamic(4.0)
fn void do_something_else() @dynamic(0, 5.0)
fn void do_another_thing() @dynamic(0, 2.5)
Importing the dynamic library and setting the base version to 4.5 and minimum version to 3.0, we get the following:
test.c3
import dynlib;
fn void test()
{
if (@available(dynlib::do_something))
{
dynlib::do_something();
}
else
{
dynlib::do_someting_else();
}
}
In this example the code would run do_something
if available
(that is, when the dynamic library is 4.0 or higher), or
fallback to do_something_else
otherwise.
If we tried to conditionally add something not available in the compilation itself, that is a compile time error:
if (@available(dynlib::do_another_thing))
{
// Error: This function is not available with 3.0
dynlib::do_another_thing();
}
Versionless dynamic loading is also possible:
maybe_dynlib.c3i
module maybe_dynlib;
fn void testme() @dynamic;
test2.c3
import maybe_dynlib;
fn void testme2()
{
if (@available(maybe_dynlib::testme))
{
dynlib::testme();
}
}
This allows things like optionally loading dynamic libraries on the platforms where this is available.
Textual Includes
$include
It’s sometimes useful to include an entire file, doing so employs the $include
function.
Includes are only valid at the top level.
File Foo.c3
module foo;
$include("Foo.x");
fn void test()
{
io::printf("%d", testX(2));
}
File Foo.x
fn testX(int i)
{
return i + 1;
}
The result is as if Foo.c3
contained the following:
module foo;
fn testX(int i)
{
return i + 1;
}
fn void test()
{
io::printf("%d", testX(2));
}
The include may use an absolute or relative path, the relative path is always relative to the source file in which the include appears.
Note that to use it, the trust level of the compiler must be set to at least 2 with
the —trust option (i.e. use --trust=include
or --trust=full
from the command line).
$exec
An alternative to $include
is $exec
which is similar to include, but instead includes the output of an external
program as the included text.
An example:
import std::io;
// On Linux or MacOS this will insert 'String a = "Hello world!";'
$exec("echo", { "String a = \\\"Hello world!\\\"\\;" });
fn void main()
{
io::printn(a);
}
Using $exec
requires full trust level, which is enabled with -trust=full
from the command line.
‘$exec’ will by default run from the /scripts
directory for projects, for non-project builds,
the current directory is used as well.
$exec
Scripting
$exec
allows a special scripting mode, where one or more C3 files are compiled on the fly and
run by $exec
.
import std::io;
// Compile foo.c3 and bar.c3 in the /scripts directory, invoke the resulting binary
// with the argument 'test'
$exec("foo.c3;bar.c3", "test");
fn void main()
{
...
}
Non-Recursive Imports
In specific circumstances you only wish to import a module without its submodules. This can be helpful in certain situations where otherwise unnecessary name-collisions would occur, but should not be used in the general case.
The syntax for non-recursive imports is import <module_name> @norecurse;
for example:
// Non-recursive import
import mylib @norecurse;
// Normal import
import mylib;
For example only importing “mylib” into “my_code” and not wishing to import “submod”.
my_code
└── mylib
└── submod
module mylib;
import std::io;
fn void only_want_this()
{
io::printn("only_want_this");
}
module mylib::submod;
import std::io;
fn void undesired_fn()
{
io::printn("undesired_fn");
}
module my_code;
// Using Non-recursive import undesired_fn not found
import mylib @norecurse;
// Using Recursive import undesired_fn is found
// import mylib;
fn void main()
{
mylib::only_want_this();
submod::undesired_fn(); // This should error
}
:::note You can import multiple modules in one line:
import lib1, lib2;
@norecurse
can be applied to one of those imports individually:
import lib1 @norecurse, lib2;
Here only lib1
is imported non-recursively and lib2
is imported normally, recursively.
:::
title: Examples description: Examples of C3 code sidebar:
order: 35
Overview
This is meant for a quick reference, to the learn more of the details, check the relevant sections.
If Statement
fn void if_example(int a)
{
if (a > 0)
{
// ..
}
else
{
// ..
}
}
For Loop
fn void example_for()
{
// the for-loop is the same as C99.
for (int i = 0; i < 10; i++)
{
io::printfn("%d", i);
}
// also equal
for (;;)
{
// ..
}
}
Foreach Loop
// Prints the values in the slice.
fn void example_foreach(float[] values)
{
foreach (index, value : values)
{
io::printfn("%d: %f", index, value);
}
}
// Updates each value in the slice
// by multiplying it by 2.
fn void example_foreach_by_ref(float[] values)
{
foreach (&value : values)
{
*value *= 2;
}
}
While Loop
fn void example_while()
{
// again exactly the same as C
int a = 10;
while (a > 0)
{
a--;
}
// Declaration
while (Point* p = getPoint())
{
// ..
}
}
Enum And Switch
Switches have implicit break and scope. Use “nextcase” to implicitly fallthrough or use comma:
enum Height : uint
{
LOW,
MEDIUM,
HIGH,
}
fn void demo_enum(Height h)
{
switch (h)
{
case LOW:
case MEDIUM:
io::printn("Not high");
// Implicit break.
case HIGH:
io::printn("High");
}
// This also works
switch (h)
{
case LOW:
case MEDIUM:
io::printn("Not high");
// Implicit break.
case Height.HIGH:
io::printn("High");
}
// Completely empty cases are not allowed.
switch (h)
{
case LOW:
break; // Explicit break required, since switches can't be empty.
case MEDIUM:
io::printn("Medium");
case HIGH:
break;
}
// special checking of switching on enum types
switch (h)
{
case LOW:
case MEDIUM:
case HIGH:
break;
default: // warning: default label in switch which covers all enumeration value
break;
}
// Using "nextcase" will fallthrough to the next case statement,
// and each case statement starts its own scope.
switch (h)
{
case LOW:
int a = 1;
io::printn("A");
nextcase;
case MEDIUM:
int a = 2;
io::printn("B");
nextcase;
case HIGH:
// a is not defined here
io::printn("C");
}
}
Enums are always namespaced.
Enum support various reflection properties: .values
returns an array with all enums. .len
or .elements
returns the number
of enum values, .inner
returns the storage type. .names
returns an array with the names of all enums. .associated
returns an array of the typeids of the associated values for the enum.
enum State : uint
{
START,
STOP,
}
State start = State.values[0];
usz enums = State.elements; // 2
String[] names = State.names; // [ "START", "STOP" ]
Defer
Defer will be invoked on scope exit.
fn void test(int x)
{
defer io::printn();
defer io::print("A");
if (x == 1) return;
{
defer io::print("B");
if (x == 0) return;
}
io::print("!");
}
fn void main()
{
test(1); // Prints "A"
test(0); // Prints "BA"
test(10); // Prints "B!A"
}
Because it’s often relevant to run different defers
when having an error return there is also a way to create an error
defer, by using the catch
keyword directly after the defer.
Similarly using defer try
can be used to only run if the scope exits in a regular way.
fn void! test(int x)
{
defer io::printn("");
defer io::print("A");
defer try io::print("X");
defer catch io::print("B");
defer (catch err) io::printf("%s", err);
if (x == 1) return SearchResult.MISSING?;
io::print("!");
}
test(0); // Prints "!XA"
test(1); // Prints "MISSINGBA" and returns a FooError
Struct Types
def Callback = fn int(char c);
enum Status : int
{
IDLE,
BUSY,
DONE,
}
struct MyData
{
char* name;
Callback open;
Callback close;
State status;
// named sub-structs (x.other.value)
struct other
{
int value;
int status; // ok, no name clash with other status
}
// anonymous sub-structs (x.value)
struct
{
int value;
int status; // error, name clash with other status in MyData
}
// anonymous union (x.person)
union
{
Person* person;
Company* company;
}
// named sub-unions (x.either.this)
union either
{
int this;
bool or;
char* that;
}
}
Function Pointers
module demo;
def Callback = fn int(char* text, int value);
fn int my_callback(char* text, int value)
{
return 0;
}
Callback cb = &my_callback;
fn void example_cb()
{
int result = cb("demo", 123);
// ..
}
Error Handling
Errors are handled using optional results, denoted with a ‘!’ suffix. A variable of an optional
result type may either contain the regular value or a fault
enum value.
fault MathError
{
DIVISION_BY_ZERO
}
fn double! divide(int a, int b)
{
// We return an optional result of type DIVISION_BY_ZERO
// when b is zero.
if (b == 0) return MathError.DIVISION_BY_ZERO?;
return (double)a / (double)b;
}
// Re-returning an optional result uses "!" suffix
fn void! testMayError()
{
divide(foo(), bar())!;
}
fn void main()
{
// ratio is an optional result.
double! ratio = divide(foo(), bar());
// Handle the optional result value if it exists.
if (catch err = ratio)
{
case MathError.DIVISION_BY_ZERO:
io::printn("Division by zero\n");
return;
default:
io::printn("Unexpected error!");
return;
}
// Flow typing makes "ratio"
// have the plain type 'double' here.
io::printfn("Ratio was %f", ratio);
}
fn void print_file(String filename)
{
String! file = io::load_file(filename);
// The following function is not called on error,
// so we must explicitly discard it with a void cast.
(void)io::printfn("Loaded %s and got:\n%s", filename, file);
if (catch err = file)
{
case IoError.FILE_NOT_FOUND:
io::printfn("I could not find the file %s", filename);
default:
io::printfn("Could not load %s.", filename);
}
}
// Note that the above is only illustrating how Optionals may skip
// call invocation. A more normal implementation would be:
fn void print_file2(String filename)
{
String! file = io::load_file(filename);
if (catch err = file)
{
// Print the error
io::printfn("Failed to load %s: %s", filename, err);
// We return, so that below 'file' will be unwrapped.
return;
}
// No need for a void cast here, 'file' is unwrappeed to 'String'.
io::printfn("Loaded %s and got:\n%s", filename, file);
}
Read more about optionals and error handling here.
Contracts
Pre- and postconditions are optionally compiled into asserts helping to optimize the code.
<*
@param foo "the number of foos"
@require foo > 0, foo < 1000
@return "number of foos x 10"
@ensure return < 10000, return > 0
*>
fn int testFoo(int foo)
{
return foo * 10;
}
<*
@param array "the array to test"
@param length "length of the array"
@require length > 0
*>
fn int getLastElement(int* array, int length)
{
return array[length - 1];
}
Read more about contracts here.
Struct Methods
It’s possible to namespace functions with a union, struct or enum type to enable “dot syntax” calls:
struct Foo
{
int i;
}
fn void Foo.next(Foo* this)
{
if (this) this.i++;
}
fn void test()
{
Foo foo = { 2 };
foo.next();
foo.next();
// Prints 4
io::printfn("%d", foo.i);
}
Macros
Macro arguments may be immediately evaluated.
macro foo(a, b)
{
return a(b);
}
fn int square(int x)
{
return x * x;
}
fn int test()
{
int a = 2;
int b = 3;
return foo(&square, 2) + a + b; // 9
// return foo(square, 2) + a + b;
// Error the symbol "square" cannot be used as an argument.
}
Macro arguments may have deferred evaluation, which is basically text expansion using #var
syntax.
macro @foo(#a, b, #c)
{
c = a(b) * b;
}
macro @foo2(#a)
{
return a * a;
}
fn int square(int x)
{
return x * x;
}
fn int test1()
{
int a = 2;
int b = 3;
@foo(square, a + 1, b);
return b; // 27
}
fn int test2()
{
return @foo2(1 + 1); // 1 + 1 * 1 + 1 = 3
}
Improve macro errors with preconditions:
<*
@param x "value to square"
@require types::is_numeric($typeof(x)) "cannot multiply"
*>
macro square(x)
{
return x * x;
}
fn void test()
{
square("hello"); // Error: cannot multiply "hello"
int a = 1;
square(&a); // Error: cannot multiply '&a'
}
Read more about macros here.
Compile Time Reflection & Execution
Access type information and loop over values at compile time:
import std::io;
struct Foo
{
int a;
double b;
int* ptr;
}
macro print_fields($Type)
{
$foreach ($field : $Type.membersof)
io::printfn("Field %s, offset: %s, size: %s, type: %s",
$field.nameof, $field.offsetof, $field.sizeof, $field.typeid.nameof);
$endforeach
}
fn void main()
{
print_fields(Foo);
}
This prints on x64:
Field a, offset: 0, size: 4, type: int
Field b, offset: 8, size: 8, type: double
Field ptr, offset: 16, size: 8, type: int*
Compile Time Execution
Macros with only compile time variables are completely evaluated at compile time:
macro long fib(long $n)
{
$if $n <= 1:
return $n;
$else
return fib($n - 1) + fib($n - 2);
$endif
}
const long FIB19 = fib(19);
// Same as const long FIB19 = 4181;
:::note C3 macros are designed to provide a replacement for C preprocessor macros. They extend such macros by providing compile time evaluation using constant folding, which offers an IDE friendly, limited, compile time execution.
However, if you are doing more complex compile time code generation it is recommended to use $exec
and related techniques to generate code in external scripts instead.
:::
Read more about compile time execution here.
Generic Modules
Generic modules implements a generic system.
module stack(<Type>);
struct Stack
{
usz capacity;
usz size;
Type* elems;
}
fn void Stack.push(Stack* this, Type element)
{
if (this.capacity == this.size)
{
this.capacity *= 2;
this.elems = realloc(this.elems, Type.sizeof * this.capacity);
}
this.elems[this.size++] = element;
}
fn Type Stack.pop(Stack* this)
{
assert(this.size > 0);
return this.elems[--this.size];
}
fn bool Stack.empty(Stack* this)
{
return !this.size;
}
Testing it out:
def IntStack = Stack(<int>);
fn void test()
{
IntStack stack;
stack.push(1);
stack.push(2);
// Prints pop: 2
io::printfn("pop: %d", stack.pop());
// Prints pop: 1
io::printfn("pop: %d", stack.pop());
Stack(<double>) dstack;
dstack.push(2.3);
dstack.push(3.141);
dstack.push(1.1235);
// Prints pop: 1.1235
io::printfn("pop: %f", dstack.pop());
}
Read more about generic modules here
Dynamic Calls
Runtime dynamic dispatch through interfaces:
import std::io;
// Define a dynamic interface
interface MyName
{
fn String myname();
}
struct Bob (MyName) { int x; }
// Required implementation as Bob implements MyName
fn String Bob.myname(Bob*) @dynamic { return "I am Bob!"; }
// Ad hoc implementation
fn String int.myname(int*) @dynamic { return "I am int!"; }
fn void whoareyou(any a)
{
MyName b = (MyName)a;
if (!&b.myname)
{
io::printn("I don't know who I am.");
return;
}
io::printn(b.myname());
}
fn void main()
{
int i = 1;
double d = 1.0;
Bob bob;
any a = &i;
whoareyou(a);
a = &d;
whoareyou(a);
a = &bob;
whoareyou(a);
}
Read more about dynamic calls here.
title: Changes From C description: Changes From C sidebar:
order: 702
Although C3 is trying to improve on C, this does not only mean addition of features, but also removal, or breaking changes:
No mandatory header files
There is a C3 interchange header format for declaring interfaces of libraries, but it is only used for special applications.
Removal of the old C macro system
The old C macro system is replaced by a new C3 macro system.
Import and modules
C3 uses module imports instead of header includes to link modules together.
Member access using .
even for pointers
The ->
operator is removed, access uses dot for both direct and pointer
access. Note that this is just single access: to access a pointer of a
pointer (e.g. int**
) an explicit dereference would be needed.
Different operator precedence
Notably bit operations have higher precedence than +/-, making code like this: a & b == c
evaluate like (a & b) == c
instead of C’s a & (b == c)
. See the page about precedence rules.
Removal of the const type qualifier
The const qualifier is only retained for actual constant variables. C3 uses a special type of post condition for functions to indicate that they do not alter in parameters.
<*
This function ensures that foo is not changed in the function.
@param [in] foo
@param [out] bar
*>
fn void test(Foo* foo, Bar* bar)
{
bar.y = foo.x;
// foo.x = foo.x + 1 - compile time error, can't write to 'in' param.
// int x = bar.y - compile time error, can't read from an 'out' param.
}
Rationale: const correctness requires littering const across the code base. Although const is useful, it provides weaker guarantees that it appears.
Fixed arrays do not decay and have copy semantics
C3 has three different array types. Variable arrays and slices decay to pointers, but fixed arrays are value objects and do not decay.
int[3] a = { 1, 2, 3 };
int[4]* b = &a; // No conversion
int* c = a; // ERROR
int* d = &a; // Valid implicit conversion
int* e = b; // Valid implicit conversion
int[3] f = a; // Copy by value!
Removal of multiple declaration syntax with initialization
Only a single declaration with initialization is allowed per statement in C3:
int i, j = 1; // ERROR
int a = 1; // Ok
int b, c; // Ok
In conditionals, a special form of multiple declarations are allowed but each must then provide its type:
for (int i = 0, int j = 1; i < 10; i++, j++) { ... }
Integer promotions rules and safe signed-unsigned comparisons
Promotion rules for integer types are different from C.
C3 allows implicit widening only
where there is only a single way to widen the expression. To explain the latter:
take the case of long x = int_val_1 + int_val_2
. In C this would widen the result of the addition:
long x = (long)(int_val_1 + int_val_2)
, but there is another possible
way to widen: long x = (long)int_val_1 + (long)int_val_2
. so in this case, the widening
is disallowed. However, long x = int_val_1
is unambiguous, so C3 permits it just like C (read more on the conversion page.
C3 also adds safe signed-unsigned comparisons: this means that comparing signed and unsigned values will always yield the correct result:
// The code below would print "Hello C3!" in C3 and "Hello C!" in C.
int i = -1;
uint j = 1;
if (i < j)
{
printf("Hello C3!\n");
}
else
{
printf("Hello C!\n");
}
Goto removed
goto
is removed and replaced with labelled break
and continue
together with the nextcase
statement that allows you to jump between cases in a switch
statement.
Rationale:
It is very difficult to make goto work well with defer and implicit
unwrapping of optional results. It is not just making the compiler
harder to write, but
the code is harder to understand as well. The replacements together with
defer
cover many if not all usages of goto
in regular code.
Implicit break in switches
Empty case
statements have implicit fall through in C3, otherwise the nextcase
statement is needed
nextcase
can also be used to jump to any other case statement in the switch.
switch (h)
{
case 1:
a = 1;
nextcase; // Fall through
case 2:
b = 123;
case 3:
a = 2;
nextcase 2; // Jump to case 2
default:
a = 111;
}
Locals variables are implicitly zeroed
In C global variables are implicitly zeroed out, but local variables aren’t.
In C3 local variables are zeroed out by default, but may be explicitly undefined
(using the @noinit
attribute) if you wish to match the C behaviour.
Rationale for this change
- In the “zero-is-initialization” paradigm, zeroing variables, in particular structs, is very common. By offering zero initialization by default this avoids a whole class of vulnerabilities.
- Another alternative that was considered for C3 was mandatory initialization, but this adds a lot of extra boilerplate.
- C3 also offers a way to opt out of zero-initialization, so the change comes at no performance loss.
Compound literal syntax changed
// C style:
call_foo((Foo) { 1, 2, 3 });
// C++ style (1):
call_foo(Foo(1, 2, 3));
// C++ style (2):
call_foo(Foo { 1, 2, 3 });
// C3:
call_foo(Foo { 1, 2, 3 } );
// C3 with inference:
call_foo({ 1, 2, 3 });
Bitfields replaced by bitstructs
Bitfields are replaced by bitstructs that have a well-defined encapsulating type, and an exact bit layout.
// C
struct Foo
{
int a : 3;
unsigned b : 4;
MyEnum c : 7;
};
struct Flags
{
bool has_hyperdrive : 1;
bool has_tractorbeam : 1;
bool has_plasmatorpedoes : 1;
}
// C3
bitstruct Foo : short
{
int a : 0..2;
uint b : 3..6;
MyEnum c : 7..13;
}
// Simple form, only allowed when all fields are bools.
struct Flags : char
{
bool has_hyperdrive;
bool has_tractorbeam;
bool has_plasmatorpedoes;
}
Evaluation order is well-defined
Evaluation order is left-to-right, and in assignment expressions, assignment happens after expression evaluation.
Signed overflow is well-defined
Signed integer overflow always wraps using 2s complement. It is never undefined behaviour.
Octal removed
The old 0777
octal syntax is removed and replaced by a 0o
prefix, e.g. 0o777
. Strings do not support octal sequences aside
from '\0'
.
title: Types description: Types sidebar:
order: 38
Overview
As usual, types are divided into basic types and user defined types (enum
, union
, struct
, fault
, def
). All types are defined on a global level.
Naming
All user defined types in C3 starts with upper case. So MyStruct
or Mystruct
would be fine, mystruct_t
or mystruct
would not.
This naming requirement ensures that the language is easy to parse for tools.
It is possible to use attributes to change the external name of a type:
struct Stat @extern("stat")
{
// ...
}
fn CInt stat(char* pathname, Stat* buf);
This would affect things like generated C headers.
Differences from C
Unlike C, C3 does not use type qualifiers. const
exists,
but is a storage class modifier, not a type qualifier.
Instead of volatile
, volatile loads and stores are used.
Restrictions on function parameter usage are instead described by parameter preconditions.
typedef
has a slightly different syntax and renamed def
.
C3 also requires all function pointers to be used with a def
for example:
def Callback = fn void();
Callback a = null; // Ok!
fn Callback getCallback() { /* ... */ } // Ok!
// fn fn void() getCallback() { /* ... */ } - ERROR!
// fn void() a = null; - ERROR!
Basic types
Basic types are divided into floating point types, and integer types. Integer types being either signed or unsigned.
Integer types
Name | bit size | signed |
---|---|---|
bool * |
1 | no |
ichar |
8 | yes |
char |
8 | no |
short |
16 | yes |
ushort |
16 | no |
int |
32 | yes |
uint |
32 | no |
long |
64 | yes |
ulong |
64 | no |
int128 |
128 | yes |
uint128 |
128 | no |
iptr ** |
varies | yes |
uptr ** |
varies | no |
isz ** |
varies | yes |
usz ** |
varies | no |
* bool
will be stored as a byte.
** size, pointer and pointer sized types depend on platform.
Integer arithmetics
All signed integer arithmetics uses 2’s complement.
Integer constants
Integer constants are 1293832 or -918212. Without a suffix, suffix type is assumed to the signed integer of arithmetic promotion width. Adding the u
suffix gives a unsigned integer of the same width. Use ixx
and uxx
– where xx
is the bit width for typed integers, e.g. 1234u16
Integers may be written in decimal, but also
- in binary with the prefix 0b e.g.
0b0101000111011
,0b011
- in octal with the prefix 0o e.g.
0o0770
,0o12345670
- in hexadecimal with the prefix 0x e.g.
0xdeadbeef
0x7f7f7f
In the case of binary, octal and hexadecimal, the type is assumed to be unsigned.
Furthermore, underscore _
may be used to add space between digits to improve readability e.g. 0xFFFF_1234_4511_0000
, 123_000_101_100
TwoCC, FourCC and EightCC
FourCC codes are often used to identify binary format types. C3 adds direct support for 4 character codes, but also 2 and 8 characters:
- 2 character strings, e.g.
'C3'
, would convert to an ushort or short. - 4 character strings, e.g.
'TEST'
, converts to an uint or int. - 8 character strings, e.g.
'FOOBAR11'
converts to an ulong or long.
Conversion is always done so that the character string has the correct ordering in memory. This means that the same characters may have different integer values on different architectures due to endianness.
Base64 and hex data literals
Base64 encoded values work like TwoCC/FourCC/EightCC, in that is it laid out in byte order in memory. It uses the format b64'<base64>'
. Hex encoded values work as base64 but with the format x'<hex>'
. In data literals any whitespace is ignored, so '00 00 11'x
encodes to the same value as x'000011'
.
In our case we could encode b64'Rk9PQkFSMTE='
as 'FOOBAR11'
.
Base64 and hex data literals initializes to arrays of the char type:
char[*] hello_world_base64 = b64"SGVsbG8gV29ybGQh";
char[*] hello_world_hex = x"4865 6c6c 6f20 776f 726c 6421";
String literals, and raw strings
Regular string literals is text enclosed in " ... "
just like in C. C3 also offers two other types of literals: multi-line strings and raw strings.
Raw strings uses text between ` `. Inside of a raw string, no escapes are available. To write a ` double the character:
char* foo = `C:\foo\bar.dll`;
char* bar = `"Say ``hello``"`;
// Same as
char* foo = "C:\\foo\\bar.dll";
char* bar = "\"Say `hello`\"";
Floating point types
Name | bit size |
---|---|
bfloat16 * |
16 |
float16 * |
16 |
float |
32 |
double |
64 |
float128 * |
128 |
*support is still incomplete.
Floating point constants
Floating point constants will at least use 64 bit precision. Just like for integer constants, it is allowed to use underscore, but it may not occur immediately before or after a dot or an exponential.
Floating point values may be written in decimal
or hexadecimal. For decimal, the exponential symbol is e (or E, both
are acceptable), for hexadecimal p (or P) is used: -2.22e-21
-0x21.93p-10
It is possible to type a floating point by adding a suffix:
Suffix | type |
---|---|
bf16 | bfloat16 |
f16 | float16 |
f32 or f | float |
f64 | double |
f128 | float128 |
C compatibility
For C compatibility the following types are also defined in std::core::cinterop
Name | c type |
---|---|
CChar |
char |
CShort |
short int |
CUShort |
unsigned short int |
CInt |
int |
CUInt |
unsigned int |
CLong |
long int |
CULong |
unsigned long int |
CLongLong |
long long |
CULongLong |
unsigned long long |
CLongDouble |
long double |
float
and double
will always match their C counterparts.
Note that signed C char and unsigned char will correspond to ichar
and char
. CChar
is only available to match the default signedness of char
on the platform.
Other built-in types
Pointer types
Pointers mirror C: Foo*
is a pointer to a Foo
, while Foo**
is a pointer to a pointer of Foo.
The typeid
type
The typeid
can hold a runtime identifier for a type. Using <typename>.typeid
a type may be converted to its unique runtime id,
e.g. typeid a = Foo.typeid;
. This value is pointer-sized.
The any
type
C3 contains a built-in variant type, which is essentially struct containing a typeid
plus a void*
pointer to a value.
While it is possible to cast the any
pointer to any pointer type,
it is recommended to use the anycast
macro or checking the type explicitly first.
fn void main()
{
int x;
any y = &x;
int* w = (int*)y; // Returns the pointer to x
double* z_bad = (double*)y; // Don't do this!
double! z = anycast(y, double); // The safe way to get a value
if (y.type == int.typeid)
{
// Do something if y contains an int*
}
}
Switching over the any
type is another method to unwrap the pointer inside:
fn void test(any z)
{
// Unwrapping switch
switch (z)
{
case int:
// z is unwrapped to int* here
case double:
// z is unwrapped to double* here
}
// Assignment switch
switch (y = z)
{
case int:
// y is int* here
}
// Direct unwrapping to a value is also possible:
switch (w = *z)
{
case int:
// w is int here
}
// Finally, if we just want to deal with the case
// where it is a single specific type:
if (z.type == int.typeid)
{
// This is safe here:
int* a = (int*)z;
}
if (try b = *anycast(z, int))
{
// b is an int:
foo(b * 3);
}
}
any.type
returns the underlying pointee typeid of the contained value. any.ptr
returns
the raw void*
pointer.
Array types
Arrays are indicated by [size]
after the type, e.g. int[4]
. Slices use the type[]
. For initialization the wildcard type[*]
can be used to infer the size
from the initializer. See the chapter on arrays.
Vector types
Vectors use [<size>]
after the type, e.g. float[<3>]
, with the restriction that vectors may only form out
of integers, floats and booleans. Similar to arrays, wildcard can be used to infer the size of a vector: int[<*>] a = { 1, 2 }
.
Types created using def
“typedef”
Like in C, C3 has a “typedef” construct, def <typename> = <type>
def Int32 = int;
def Vector2 = float[<2>];
/* ... */
Int32 a = 1;
int b = a;
Function pointer types
Function pointers are always used through a def
:
def Callback = fn void(int value);
Callback callback = &test;
fn void test(int a) { /* ... */ }
To form a function pointer, write a normal function declaration but skipping the function name. fn int foo(double x)
->
fn int(double x)
.
Function pointers can have default arguments, e.g. def Callback = fn void(int value = 0)
but default arguments
and parameter names are not taken into account when determining function pointer assignability:
def Callback = fn void(int value = 1);
fn void test(int a = 0) { /* ... */ }
Callback callback = &main; // Ok
fn void main()
{
callback(); // Works, same as test(0);
test(); // Works, same as test(1);
callback(.value = 3); // Works, same as test(3)
test(.a = 4); // Works, same as test(4)
// callback(.a = 3); ERROR!
}
Distinct types
Distinct types is a kind of type alias which creates a new type that has the same properties as the original type
but is - as the name suggests - distinct from it. It cannot implicitly convert into the other type using the syntax
distict <name> = <type>
distinct MyId = int;
fn void* get_by_id(MyId id) { ... }
fn void test(MyId id)
{
void* val = get_by_id(id); // Ok
void* val2 = get_by_id(1); // Literals convert implicitly
int a = 1;
// void* val3 = get_by_id(a); // ERROR expected a MyId
void* val4 = get_by_id((MyId)a); // Works
// a = id; // ERROR can't assign 'MyId' to 'int'
}
Inline distinct
Using inline
in the distinct
declaration allows a distinct type to implicitly convert to its underlying type:
distinct Abc = int;
distinct Bcd = inline int;
fn void test()
{
Abc a = 1;
Bcd b = 1;
// int i = a; Error: Abc cannot be implicitly converted to 'int'
int i = b; // This is valid
// However, 'inline' does not allow implicit conversion from
// the inline type to the distinct type:
// a = i; Error: Can't implicitly convert 'int' to 'Abc'
// b = i; Error: Can't implicitly convert 'int' to 'Bcd'
}
Generic types
import generic_list; // Contains the generic MyList
struct Foo {
int x;
}
// ✅ def for each type used with a generic module.
def IntMyList = MyList(<Foo>);
MyListFoo working_example;
// ❌ An inline type definition will give an error.
// Only allowed in a type definition or macro
// To avoid this A type may be declared with @adhoc
MyList<Foo> failing_example = MyList(<Foo>);
Find out more about generic types.
Enum
Enum or enumerated types use the following syntax:
enum State : int
{
WAITING,
RUNNING,
TERMINATED
}
// Access enum values via:
State current_state = State.WAITING;
The access requires referencing the enum
‘s name as State.WAITING
because
an enum like State
is a separate namespace by default, just like C++’s class enum
.
Enum associated values
It is possible to associate each enum value with one or more a static values.
enum State : int (String description)
{
WAITING = "waiting",
RUNNING = "running",
TERMINATED = "ended",
}
fn void main()
{
State process = State.RUNNING;
io::printfn("%s", process.description);
}
Multiple static values can be associated with an enum value, for example:
struct Position
{
int x;
int y;
}
enum State : int (String desc, bool active, Position pos)
{
WAITING = { "waiting", false, { 1, 2} },
RUNNING = { "running", true, {12,22} },
TERMINATED = { "ended", false, { 0, 0} },
}
fn void main()
{
State process = State.RUNNING;
if (process.active)
{
io::printfn("Process is: %s", process.desc);
io::printfn("Position x: %d", process.pos.x);
}
}
Enum type inference
When an enum
is used where the type can be inferred, like in switch case-clauses or in variable assignment, the enum name is not required:
State process = WAITING; // State.WAITING is inferred.
switch (process)
{
case RUNNING: // State.RUNNING is inferred
io::printfn("Position x: %d", process.pos.x);
default:
io::printfn("Process is: %s", process.desc);
}
fn void test(State s) { ... }
test(RUNNING); // State.RUNNING is inferred
If the enum
without it’s name matches with a global in the same scope, it needs the enum name to be added as a qualifier, for example:
module test;
// Global variable
// ❌ Don't do this!
const State RUNNING = State.TERMINATED;
test(RUNNING); // Ambiguous
test(test::RUNNING); // Uses global variable.
test(State.RUNNING); // Uses enum constant.
Optional Type
An Optional type is created by taking a type and appending !
.
An Optional type behaves like a tagged union, containing either the
result or an Excuse that is of a fault type.
Once extracted, any specific fault can be converted to an anyfault
.
int! i;
i = 5; // Assigning a real value to i.
i = IOResult.IO_ERROR?; // Assigning an optional result to i.
anyfault b = SearchError.MISSING;
b = @catch(i); // Assign the Excuse in i to b (IO_ERROR)
Only variables, expressions and function returns may be Optionals. Function and macro parameters in their definitions may not be optionals.
fn Foo*! getFoo() { /* ... */ } // ✅ Ok!
int! x = 0; // ✅ Ok!
fn void processFoo(Foo*! f) { /* ... */ } // ❌ fn paramater
Read more about the Optional types on the page about Optionals and error handling.
Optional Excuses are of type Fault
When an Optional does not contain a result, it is empty, and has an Excuse, which is afault
.
The anyfault
type may contain any such fault.
fault IOResult
{
IO_ERROR,
PARSE_ERROR
}
fault MapResult
{
NOT_FOUND
}
Like the typeid type, the constants are pointer sized
and each value is globally unique. For example the underlying value of
MapResult.NOT_FOUND
is guaranteed to be different from IOResult.IO_ERROR
.
This is true even if they are separately compiled.
:::note The underlying values assigned to a fault may vary each time a program is compiled. :::
A fault may be stored as a normal value, but is also unique so that it may be passed
in an Optional as a function return value using the
rethrow !
operator.
Struct types
Structs are always named:
struct Person
{
char age;
String name;
}
A struct’s members may be accessed using dot notation, even for pointers to structs.
fn void test()
{
Person p;
p.age = 21;
p.name = "John Doe";
io::printfn("%s is %d years old.", p.name, p.age);
Person* p_ptr_ = &p;
p_ptr.age = 20; // Ok!
io::printfn("%s is %d years old.", p_ptr.name, p_ptr.age);
}
(One might wonder whether it’s possible to take a Person**
and use dot access. – It’s not allowed, only one level of dereference is done.)
To change alignment and packing, attributes such as @packed
may be used.
Struct subtyping
C3 allows creating struct subtypes using inline
:
struct ImportantPerson
{
inline Person person;
String title;
}
fn void print_person(Person p)
{
io::printfn("%s is %d years old.", p.name, p.age);
}
fn void test()
{
ImportantPerson important_person;
important_person.age = 25;
important_person.name = "Jane Doe";
important_person.title = "Rockstar";
// Only the first part of the struct is copied.
print_person(important_person);
}
Union types
Union types are defined just like structs and are fully compatible with C.
union Integral
{
char as_byte;
short as_short;
int as_int;
long as_long;
}
As usual unions are used to hold one of many possible values:
fn void test()
{
Integral i;
i.as_byte = 40; // Setting the active member to as_byte
i.as_int = 500; // Changing the active member to as_int
// Undefined behaviour: as_byte is not the active member,
// so this will probably print garbage.
io::printfn("%d\n", i.as_byte);
}
Note that unions only take up as much space as their largest member, so Integral.sizeof
is equivalent to long.sizeof
.
Nested sub-structs / unions
Just like in C99 and later, nested anonymous sub-structs / unions are allowed. Note that the placement of struct / union names is different to match the difference in declaration.
struct Person
{
char age;
String name;
union
{
int employee_nr;
uint other_nr;
}
union subname
{
bool b;
Callback cb;
}
}
Bitstructs
Bitstructs allows storing fields in a specific bit layout. A bitstruct may only contain integer types and booleans, in most other respects it works like a struct.
The main differences is that the bitstruct has a backing type and each field has a specific bit range. In addition, it’s not possible to take the address of a bitstruct field.
bitstruct Foo : char
{
int a : 0..2;
int b : 4..6;
bool c : 7;
}
fn void test()
{
Foo f;
f.a = 2;
char x = (char)f;
io::printfn("%d", (char)f); // prints 2
f.b = 1;
io::printfn("%d", (char)f); // prints 18
f.c = true;
io::printfn("%d", (char)f); // prints 146
}
The bitstruct will follow the endianness of the underlying type:
bitstruct Test : uint
{
ushort a : 0..15;
ushort b : 16..31;
}
fn void test()
{
Test t;
t.a = 0xABCD;
t.b = 0x789A;
char* c = (char*)&t;
// Prints 789AABCD
io::printfn("%X", (uint)t);
for (int i = 0; i < 4; i++)
{
// Prints CDAB9A78
io::printf("%X", c[i]);
}
io::printn();
}
It is however possible to pick a different endianness, in which case the entire representation will internally assume big endian layout:
bitstruct Test : uint @bigendian
{
ushort a : 0..15;
ushort b : 16..31;
}
In this case the same example yields CDAB9A78
and 789AABCD
respectively.
Bitstruct backing types may be integers or char arrays. The difference in layout is somewhat subtle:
bitstruct Test1 : char[4]
{
ushort a : 0..15;
ushort b : 16..31;
}
bitstruct Test2 : char[4] @bigendian
{
ushort a : 0..15;
ushort b : 16..31;
}
fn void test()
{
Test1 t1;
Test2 t2;
t1.a = t2.a = 0xABCD;
t1.b = t2.b = 0x789A;
char* c = (char*)&t1;
for (int i = 0; i < 4; i++)
{
// Prints CDAB9A78 on x86
io::printf("%X", c[i]);
}
io::printn();
c = (char*)&t2;
for (int i = 0; i < 4; i++)
{
// Prints ABCD789A
io::printf("%X", c[i]);
}
io::printn();
}
Bitstructs can be made to have overlapping bit fields. This is useful when modelling a layout which has multiple different layouts depending on flag bits:
bitstruct Foo : char @overlap
{
int a : 2..5;
// "b" is valid due to the @overlap attribute
int b : 1..3;
}
title: Arrays description: Arrays sidebar:
order: 60
Arrays have a central role in programming. C3 offers built-in arrays, slices and vectors. The standard library enhances this further with dynamically sized arrays and other collections.
Fixed Size 1D Arrays
These are declared as <type>[<size>]
, e.g. int[4]
.
Fixed arrays are treated as values and will be copied if given as
parameter. Unlike C, the number is part of its type. Taking a pointer to
a fixed array will create a pointer to a fixed array, e.g. int[4]*
.
Unlike C, fixed arrays do not decay into pointers. Instead, an int[4]*
may be implicitly converted into an int*
.
// C
int foo(int *a) { ... }
int x[3] = { 1, 2, 3 };
foo(x);
// C3
fn int foo(int *a) { ... }
int[3] x = { 1, 2, 3 };
foo(&x);
When you want to initialize a fixed array without specifying the size, use the [*] array syntax:
int[3] a = { 1, 2, 3 };
int[*] b = { 4, 5, 6 }; // Type inferred to be int[3]
Slice
The final type is the slice <type>[]
e.g. int[]
.
A slice is a view into either a fixed or variable array. Internally it
is represented as a struct containing a pointer and a size. Both fixed
and variable arrays may be converted into slices, and slices may be
implicitly converted to pointers.
fn void test()
{
int[4] arr = { 1, 2, 3, 4 };
int[4]* ptr = &arr;
// Assignments to slices
int[] slice1 = &arr; // Implicit conversion
int[] slice2 = ptr; // Implicit conversion
// Assignments from slices
int[] slice3 = slice1; // Assign slices from other slices
int* int_ptr = slice1; // Assign from slice
int[4]* arr_ptr = (int[4]*)slice1; // Cast from slice
}
Slicing Arrays
It’s possible to use the range syntax to create slices from pointers, arrays, and other slices.
This is written arr[<start-index> .. <end-index>]
, where the end-index
is inclusive.
fn void test()
{
int[5] a = { 1, 20, 50, 100, 200 };
int[] b = a[0 .. 4]; // The whole array as a slice.
int[] c = a[2 .. 3]; // { 50, 100 }
}
You can also use the arr[<start-index> : <slice-length>]
fn void test()
{
int[5] a = { 1, 20, 50, 100, 200 };
int[] b2 = a[0 : 5]; // { 1, 20, 50, 100, 200 } start-index 0, slice-length 5
int[] c2 = a[2 : 2]; // { 50, 100 } start-index 2, slice-length 2
}
It’s possible to omit the first and last indices of a range:
- arr[..<end-index>]
Omitting the start-index
will default it to 0
- arr[<start-index>..]
Omitting the end-index
will assign it to arr.len-1
(this is not allowed on pointers)
Equivalently with index offset arr[:<slice-length>]
you can omit the start-index
The following are all equivalent and slice the whole array
fn void test()
{
int[5] a = { 1, 20, 50, 100, 200 };
int[] b = a[0 .. 4];
int[] c = a[..4];
int[] d = a[0..];
int[] e = a[..];
int[] f = a[0 : 5];
int[] g = a[:5];
}
You can also slice in reverse from the end with ^i
where the index is len-i
for example:
- ^1
means len-1
- ^2
means len-2
- ^3
means len-3
Again, this is not allowed for pointers since the length is unknown.
fn void test()
{
int[5] a = { 1, 20, 50, 100, 200 };
int[] b1 = a[1 .. ^1]; // { 20, 50, 100, 200 } a[1 .. (a.len-1)]
int[] b2 = a[1 .. ^2]; // { 20, 50, 100 } a[1 .. (a.len-2)]
int[] b3 = a[1 .. ^3]; // { 20, 50 } a[1 .. (a.len-3)]
int[] c1 = a[^1..]; // { 200 } a[(a.len-1)..]
int[] c2 = a[^2..]; // { 100, 200 } a[(a.len-2)..]
int[] c3 = a[^3..]; // { 50, 100, 200 } a[(a.len-3)..]
int[] d = a[^3 : 2]; // { 50, 100 } a[(a.len-3) : 2]
// Slicing a whole array, the inclusive index of : gives the difference
int[] e = a[0 .. ^1]; // a[0 .. a.len-1]
int[] f = a[0 : ^0]; // a[0 : a.len]
}
One may also assign to slices:
int[3] a = { 1, 20, 50 };
a[1..2] = 0; // a = { 1, 0, 0 }
Or copy slices to slices:
int[3] a = { 1, 20, 50 };
int[3] b = { 2, 4, 5 };
a[1..2] = b[0..1]; // a = { 1, 2, 4 }
Copying between two overlapping ranges, e.g. a[1..2] = a[0..1]
is unspecified behaviour.
Conversion List
int[4] | int[] | int[4]* | int* | |
---|---|---|---|---|
int[4] | copy | - | - | - |
int[] | - | assign | assign | - |
int[4]* | - | cast | assign | cast |
int* | - | assign | assign | assign |
Note that all casts above are inherently unsafe and will only work if the type cast is indeed compatible.
For example:
int[4] a;
int[4]* b = &a;
int* c = b;
// Safe cast:
int[4]* d = (int[4]*)c;
int e = 12;
int* f = &e;
// Incorrect, but not checked
int[4]* g = (int[4]*)f;
// Also incorrect but not checked.
int[] h = f[0..2];
Internals
Internally the layout of a slice is guaranteed to be struct { <type>* ptr; usz len; }
.
There is a built-in struct std::core::runtime::SliceRaw
which
has the exact data layout of the fat array pointers. It is defined to be
struct SliceRaw
{
void* ptr;
usz len;
}
Iteration Over Arrays
Foreach element by copy
You may iterate over slices, arrays and vectors using foreach (Type x : array)
.
Using compile-time type inference this can be abbreviated
to foreach (x : array)
for example:
fn void test()
{
int[4] arr = { 1, 2, 3, 5 };
foreach (item : arr)
{
io::printfn("item: %s", item);
}
// Or equivalently, writing the type:
foreach (int x : arr)
{
/* ... */
}
}
Foreach element by reference
Using &
it is possible to get an element by reference rather than by copy.
Providing two variables to foreach
, the first is assumed to be the index and the second the value:
fn void test()
{
float[4] arr = { };
foreach (idx, &item : arr)
{
item = 7 + idx; // Mutates the array element
}
// Or equivalently, writing the types
foreach (int idx, Foo* &&item : arr)
{
item = 7 + idx; // Mutates the array element
}
}
Foreach_r reverse iterating
With foreach_r
arrays or slices can be iterated over in reverse order
fn void test()
{
float[4] arr = { 1.0, 2.0 };
foreach_r (idx, item : arr)
{
// Prints 2.0, 1.0
io::printfn("item: %s", item);
}
// Or equivalently, writing the types
foreach_r (int idx, float item : arr)
{
// Prints 2.0, 1.0
io::printfn("item: %s", item);
}
}
Iteration Over Array-Like types
It is possible to enable foreach on any custom type
by implementing .len
and []
methods and annotating them using the @operator
attribute:
struct DynamicArray
{
usz count;
usz capacity;
int* elements;
}
macro int DynamicArray.get(DynamicArray* arr, usz element) @operator([])
{
return arr.elements[element];
}
macro usz DynamicArray.count(DynamicArray* arr) @operator(len)
{
return arr.count;
}
fn void DynamicArray.push(DynamicArray* arr, int value)
{
arr.ensure_capacity(arr.count + 1); // Function not shown in example.
arr.elements[arr.count++] = value;
}
fn void test()
{
DynamicArray v;
v.push(3);
v.push(7);
// Will print 3 and 7
foreach (int i : v)
{
io::printfn("%d", i);
}
}
For more information, see operator overloading
Dynamic Arrays and Lists
The standard library offers dynamic arrays and other collections in the std::collections
module.
def ListStr = List(<String>);
fn void test()
{
ListStr list_str;
// Initialize the list on the heap.
list_str.new_init();
list_str.push("Hello"); // Add the string "Hello"
list_str.push("World");
foreach (str : list_str)
{
io::printn(str); // Prints "Hello", then "World"
}
String str = list_str[1]; // str == "World"
list_str.free(); // Free all memory associated with list.
}
Fixed Size Multi-Dimensional Arrays
To declare two dimensional fixed arrays as <type>[<x-size>, <y-size>] arr
, like int[4][2] arr
. Below you can see how this compares to C:
// C
// Uses: name[<rows>][<columns>]
int array_in_c[4][2] = {
{1, 2},
{3, 4},
{5, 6},
{7, 8},
};
// C3
// Uses: <type>[<x-size>][<y-size>]
// C3 declares the dimensions, inner-most to outer-most
int[4][2] array = {
{1, 2, 3, 4},
{5, 6, 7, 8},
};
// To match C we must invert the order of the dimensions
int[2][4] array = {
{1, 2},
{3, 4},
{5, 6},
{7, 8},
};
// C3 also supports Irregular arrays, for example:
int[][4] array = {
{ 1 },
{ 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9, 10 },
};
:::note Accessing the multi-dimensional fixed array has inverted array index order to when the array was declared.
// Uses: <type>[<x-size>][<y-size>]
int[2][4] array = {
{1, 2},
{3, 4},
{5, 6},
{7, 8},
};
// Access fixed array using: array[<row>][<column>]
int value = array[3][1]; // 8
:::
title: Define
description: The def
statement
sidebar:
order: 61
The “def” statement
The def
statement in C3 is intended for aliasing identifiers and types.
Defining a type alias
def <type alias> = <type>
creates a type alias. Type aliases need to follow the name convention of user defined types (i.e. capitalized
names with at least one lower case letter).
def CharPtr = char*;
def Numbers = int[10];
Function pointers must be aliased in C3. The syntax is somewhat different from C:
def Callback = fn void(int a, bool b);
This defines an alias to function pointer type of a function that returns nothing and requires two arguments: an int and a bool. Here is a sample usage:
Callback cb = my_callback;
cb(10, false);
Distinct types
Similar to def
aliases are distinct
which create distinct new types. Unlike type aliases,
they do not implicitly convert to or from any other type.
Literals will convert to the distinct types if they would convert to the underlying type.
Because a distinct type is a standalone type, it can have its own methods, like any other user-defined type.
distinct Foo = int;
Foo f = 0; // Valid since 0 converts to an int.
f = f + 1;
int i = 1;
// f = f + i Error!
f = f + (Foo)i; // Valid
Distinct inline
When interacting with various APIs it is sometimes desirable for distinct types to implicitly convert to its base type, but not from that type.
Behaviour here is analogous how structs may use inline
to create struct subtypes.
distinct CString = char*;
distinct ZString = inline char*;
...
CString abc = "abc";
ZString def = "def";
// char* from_abc = abc; // Error!
char* from_def = def; // Valid!
Function and variable aliases
def
can also be used to create aliases for functions and variables.
The syntax is def <alias> = <original identifier>
.
fn void foo() { ... }
int foo_var;
def bar = foo;
def bar_var = foo_var;
fn void test()
{
// These are the same:
foo();
bar();
// These access the same variable:
int x = foo_var;
int y = bar_var;
}
Using def
to create generic types, functions and variables
It is recommended to favour using def to create aliases for parameterized types, but it can also be used for parameterized functions and variables:
import generic_foo;
// Parameterized function aliases
def int_foo_call = generic_foo::foo_call(<int>);
def double_foo_call = generic_foo::foo_call(<double>);
// Parameterized type aliases
def IntFoo = Foo(<int>);
def DoubleFoo = Foo(<double>);
// Parameterized global aliases
def int_max_foo = generic_foo::max_foo(<int>);
def double_max_foo = generic_foo::max_foo(<double>);
For more information, see the chapter on generics.
Function pointer default arguments and named parameters
It is possible to attach default arguments to function pointer aliases. There is no requirement that the function has the same default arguments. In fact, the function pointer may have default arguments where the function doesn’t have it and vice-versa. Calling the function directly will then use the function’s default arguments, and calling through the function pointer will yield the function pointer alias’ default argument.
Similarly, named parameter arguments follow the alias definition when calling through the function pointer:
def TestFn = fn void(int y = 123);
fn void test(int x = 5)
{
io::printfn("X = %d", x);
}
fn void main()
{
TestFn test2 = &test;
test(); // Prints X = 5
test2(); // Prints X = 123
test(x: 3); // Prints X = 3
test2(y: 4); // Prints X = 4
}
title: Strings description: Strings sidebar:
order: 62
In C3, multiple string types are available, each suited to different use cases.
String
distinct String = inline char[];
\
Strings are usually the typical type to use, they can be sliced , compared etc … \
It is possible to access the length of a String
instance through the .len
operator.
ZString
distinct ZString = inline char*;
\
ZString is used when working with C code, which expects null-terminated C-style strings of type char*
. it is a distinct type so converting to a ZString
requires an explicit cast. This helps to remind the user to check there is appropriate \0
termination of the string data.
The ZString methods are outlined below.
:::caution
Ensure the terminal \0
when converting from String
to ZString
.
:::
WString
distinct WString = inline Char16*;
\
The WString
type is similar to ZString
but uses Char16*
, typically for UTF-16 encoded strings. This type is useful for applications where 16-bit character encoding is required.
DString
distinct DString (OutStream) = void*;
\
DString
is a dynamic string builder that supports various
string operations at runtime, allowing for flexible manipulation without
the need for manual memory allocation.
Member functions:
String Member Functions
fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap())
fn Char16[]! String.to_temp_utf16(s);
fn WString! String.to_wstring(s, Allocator allocator)
```c3 implementation fn String String.free(&s, Allocator allocator = allocator::heap())
```c3 implementation
fn String String.tcopy(s) => s.copy(allocator::temp()) @inline;
```c3 implementation fn String String.copy(s, Allocator allocator = allocator::heap())
```c3 implementation
fn String String.strip_end(string, String needle);
```c3 implementation fn String String.strip(string, String needle);
```c3 implementation
fn String String.trim(string, String to_trim);
```c3 implementation fn bool String.contains(string, String needle);
```c3 implementation
fn bool String.starts_with(string, String needle);
```c3 implementation fn bool String.ends_with(string, String needle);
```c3 implementation
fn usz! String.index_of_char(s, char needle);
```c3 implementation fn usz! String.index_of_char_from(s, char needle, usz start_index);
```c3 implementation
fn usz! String.index_of(s, String needle)
fn usz! String.rindex_of(s, String needle)
```c3 implementation fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap());
```c3 implementation
fn String String.new_split(s, String needle, usz max = 0) => s.split(needle, max, allocator::heap()) @inline;
```c3 implementation // temporary String split fn String String.tsplit(s, String needle, usz max = 0) => s.split(needle, max, allocator::temp());
```c3 implementation
fn String String.tconcat(s1, String s2);
```c3 implementation fn String String.tconcat(s1, String s2) => s1.concat(s2, allocator::temp());
```c3 implementation
fn WString! String.to_temp_wstring(s) => s.to_wstring(allocator::temp());
```c3 implementation fn WString! String.to_new_wstring(s) => s.to_wstring(allocator::heap());
```c3 implementation
fn int128! String.to_int128(s, int base = 10) => s.to_integer(int128, base);
```c3 implementation fn long! String.to_long(s, int base = 10) => s.to_integer(long, base);
```c3 implementation
fn int! String.to_int(s, int base = 10) => s.to_integer(int, base);
```c3 implementation fn short! String.to_short(s, int base = 10) => s.to_integer(short, base);
```c3 implementation
fn ichar! String.to_ichar(s, int base = 10) => s.to_integer(ichar, base);
```c3 implementation fn uint128! String.to_uint128(s, int base = 10) => s.to_integer(uint128, base);
```c3 implementation
fn ulong! String.to_ulong(s, int base = 10) => s.to_integer(ulong, base);
```c3 implementation fn uint! String.to_uint(s, int base = 10) => s.to_integer(uint, base);
```c3 implementation
fn ushort! String.to_ushort(s, int base = 10) => s.to_integer(ushort, base);
```c3 implementation fn char! String.to_uchar(s, int base = 10) => s.to_integer(char, base);
```c3 implementation
fn double! String.to_double(s) => s.to_real(double);
```c3 implementation fn float! String.to_float(s) => s.to_real(float);
```c3 implementation
fn String String.new_ascii_to_upper(s, Allocator allocator = allocator::heap());
```c3 implementation fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap());
```c3 implementation
fn Char16[]! String.to_temp_utf16(s);
```c3 implementation fn Char32[]! String.to_utf32(s, Allocator allocator);
```c3 implementation
fn Char32[]! String.to_new_utf32(s) => s.to_utf32(allocator::heap()) @inline;
```c3 implementation fn Char32[]! String.to_temp_utf32(s) => s.to_utf32(allocator::temp()) @inline;
```c3 implementation
fn WString! String.to_wstring(s, Allocator allocator);
fn WString! String.to_new_wstring(s) => s.to_wstring(allocator::heap());
fn WString! String.to_temp_wstring(s) => s.to_wstring(allocator::temp());
fn StringIterator String.iterator(s);
ZString Member Functions
```c3 implementation fn String ZString.str_view(str);
```c3 implementation
fn usz ZString.char_len(str);
```c3 implementation fn usz ZString.len(str);
```c3 implementation
fn ZString String.zstr_copy(s, Allocator allocator = allocator::heap())
```c3 implementation fn ZString String.zstr_tcopy(s) => s.zstr_copy(allocator::temp()) @inline;
---
title: Vectors
description: Vectors
sidebar:
order: 63
---
Vectors - where possible - based on underlying hardware vector implementations. A vector is similar to an array, but
with additional functionality. The restriction is that a vector may only consist of elements that are numerical
types, boolean or pointers.
A vector is declared similar to an array but uses `[<>]` rather than `[]`, e.g. `int[<4>]`.
(If you are searching for the counterpart of C++'s `std::vector`, look instead at the standard
library [`List` type](/language-common/arrays/#dynamic-arrays-and-lists).)
## Arithmetics on vectors
Vectors support all arithmetics and other operations supported by its underlying type. The operations are
always performed elementwise.
```c3
int[<2>] a = { 23, 11 };
int[<2>] b = { 2, 1 };
int[<2>] c = a * b; // c = { 46, 11 }
For integer and boolean types, bit operations such as ^ | & << >>
are available, and for pointers, pointer arithmetic
is supported.
Scalar values
Scalar values will implicitly widen to vectors when used with vectors:
int[<2>] d = { 21, 14 };
int[<2>] e = d / 7; // e = { 3, 2 }
int[<2>] f = 4; // f = { 4, 4 }
Additional operations
The std::math
module contains a wealth of additional operations available on vectors using dot-method syntax.
.sum()
- sum all vector elements..product()
- multiply all vector elements..max()
- get the maximum element..min()
- get the minimum element..dot(other)
- return the dot product with the other vector..length(other)
- return the square root of the dot product (not available on integer vectors)..distance(other)
- return the length of the difference of the two vectors (not available on integer vectors)..normalize()
- return a normalized vector (not available on integer vectors)..comp_lt(other)
- return a boolean vector with a component wise “<”.comp_le(other)
- return a boolean vector with a component wise “<=”.comp_eq(other)
- return a boolean vector with a component wise “==”.comp_gt(other)
- return a boolean vector with a component wise “>”.comp_ge(other)
- return a boolean vector with a component wise “>=”.comp_ne(other)
- return a boolean vector with a component wise “!=”
Dot methods available for scalar values, such as ceil
, fma
etc are in general also available for vectors.
Swizzling
Swizzling using dot notation is supported, using x, y, z, w or r, g, b, a:
int[<3>] a = { 11, 22, 33 };
int[<4>] b = a.xxzx; // b = { 11, 11, 33, 11 }
int c = b.w; // c = 11;
char[<4>] color = { 0x11, 0x22, 0x33, 0xFF };
char red = color.r; // red = 0x11
Array-like operations
Like arrays, it’s possible to make slices and iterate over vectors. It should be noted that the storage alignment of vectors are often different from arrays, which should be taken into account when storing vectors.
title: Essential Error Handling description: Essential Error Handling sidebar:
order: 64
In this section we will go over the essential information about Optionals and safe methods for working with them, for example
if (catch optional_value)
and the Rethrow operator !
.
In the advanced section there are other nice to have features.
Like an alternative to safely unwrap a result from an Optionals using
if (try optional_value)
and an unsafe method to force unwrap !!
a result from an Optional, return default values for optionals ??
if they are empty and other more specialised concepts.
What is an Optional?
Optionals are a safer alternative to returning -1
or null
from
a function, when a valid value can’t be returned. An Optional
has either a result or is empty. When an Optional
is empty it has an Excuse
explaining what happened.
- For example trying to open a missing file returns the
Excuse
ofIoError.FILE_NOT_FOUND
. - Optionals are declared by adding
!
after the type. - An
Excuse
is of the typeanyfault
.
The Optional Excuse is set withint! a = 1; // Set the Optional to a result
?
after the value.// Set the Optional to empty with a specific Excuse. int! b = IoError.FILE_NOT_FOUND?;
🎁 Unwrapping an Optional
:::note
Unwrapping an Optional is safe because it checks it has a result present before trying to use it.
After unwrapping, the variable then behaves like a normal variable, a non-Optional. :::
Checking if an Optional is empty
import std::io;
fn void! test()
{
// Return an Excuse by adding '?' after the fault.
return IoError.FILE_NOT_FOUND?;
}
fn void main(String[] args)
{
// If the Optional is empty, assign the
// Excuse to a variable:
if (catch excuse = test())
{
io::printfn("test() gave an Excuse: %s", excuse);
}
}
Automatically unwrapping an Optional result
If we escape the current scope from an if (catch my_var)
using a return
, break
, continue
or Rethrow !
,
then the variable is automatically unwrapped to a non-Optional:
fn void! test()
{
int! foo = unreliable_function();
if (catch excuse = foo)
{
// Return the excuse with `?` operator
return excuse?;
}
// Because the compiler knows 'foo' cannot
// be empty here, it is unwrapped to non-Optional
// 'int foo' in this scope:
io::printfn("foo: %s", foo); // 7
}
Using the Rethrow operator !
to unwrap an Optional value
- The Rethrow operator
!
will return from the function with theExcuse
if the Optional result is empty. - The resulting value will be unwrapped to a non-Optional.
import std::io;
// Function returning an Optional
fn int! maybe_func() { /* ... */ }
fn void! test()
{
// ❌ This will be a compile error
// maybe_function() returns an Optional
// and 'bar' is not declared Optional:
// int bar = maybe_function();
int bar = maybe_function()!;
// ✅ The above is equivalent to:
// int! temp = maybe_function();
// if (catch excuse = temp) return excuse?
// Now temp is unwrapped to a non-Optional
int bar = temp; // ✅ This is OK
}
⚠️ Optionals affect types and control flow
Optionals in expressions produce Optionals
Use an Optional anywhere in an expression the resulting expression will be an Optional too.
import std::io;
fn void main(String[] args)
{
// Returns Optional with result of type `int` or an Excuse
int! first_optional = 7;
// This is Optional too:
int! second_optional = first_optional + 1;
}
Optionals affect function return types
import std::io;
fn int test(int input)
{
io::printn("test(): inside function body");
return input;
}
fn void main(String[] args)
{
int! optional_argument = 7;
// `optional_argument` makes returned `returned_optional`
// Optional too:
int! returned_optional = test(optional_argument);
}
Functions conditionally run when called with Optional arguments
When calling a function with an Optionals as arguments, the result will be the first Excuse found looking left-to-right. The function is only executed if all Optional arguments have a result.
import std::io;
fn int test(int input, int input2)
{
io::printn("test(): inside function body");
return input;
}
fn void main(String[] args)
{
int! first_optional = IoError.FILE_NOT_FOUND?;
int! second_optional = 7;
// Return first excuse we find
int! third_optional = test(first_optional, second_optional);
if (catch excuse = third_optional)
{
// excuse == IoError.FILE_NOT_FOUND
io::printfn("third_optional's Excuse: %s", excuse);
}
}
Interfacing with C
For C the interface to C3:
- The Excuse
in the Optional of type anyfault
is returned as the regular return.
- The result in the Optional is passed by reference.
For example:
// C3 code:
fn int! get_value();
// Corresponding C code:
c3fault_t get_value(int *value_ref);
The c3fault_t
is guaranteed to be a pointer sized value.
title: Advanced Error Handling description: Advanced Error Handling sidebar:
order: 65
Optionals are only defined in certain code
✅ Variable declarations
int! example = unreliable_function();
✅ Function return signature
fn int! example() { /* ... */ }
Handling an empty Optional
File reading example
- If the file is present the Optional result will be the first 100 bytes of the file.
- If the file is not present the Optional
Excuse
will beIoError.FILE_NOT_FOUND
.
Try running this code below with and without a file called file_to_open.txt
in the same directory.
import std::io;
<*
Function modifies 'buffer'
Returns an Optional with a 'char[]' result
OR an empty Optional with an Excuse
*>
fn char[]! read_file(String filename, char[] buffer)
{
// Return Excuse if opening a file failed, using Rethrow `!`
File file = file::open(filename, "r")!;
// At scope exit, close the file.
// Discard the Excuse from file.close() with (void) cast
defer (void)file.close();
// Return Excuse if reading failed, using Rethrow `!`
file.read(buffer)!;
return buffer; // return a buffer result
}
fn void! test_read()
{
char[] buffer = mem::new_array(char, 100);
defer free(buffer); // Free memory on scope exit
char[]! read_buffer = read_file("file_to_open.txt", buffer);
// Catch the empty Optional and assign the Excuse
// to `excuse`
if (catch excuse = read_buffer)
{
io::printfn("Excuse found: %s", excuse);
// Returning Excuse using the `?` suffix
return excuse?;
}
// `read_buffer` behaves like a normal variable here
// because the Optional being empty was handled by 'if (catch)'
// which automatically unwrapped 'read_buffer' at this point.
io::printfn("read_buffer: %s", read_buffer);
}
fn void main()
{
test_read()!!; // Panic on failure.
}
Return a default value if Optional is empty
The ??
operator allows us to return a default value if the Optional is empty.
import std::io;
fn void test_bad()
{
int regular_value;
int! optional_value = function_may_error();
// An empty Optional found in optional_value
if (catch optional_value)
{
// Assign default result when empty.
regular_value = -1;
}
// A result was found in optional_value
if (try optional_value)
{
regular_value = optional_value;
}
io::printfn("The value was: %d", regular_value);
}
fn void test_good()
{
// Return '-1' when `foo_may_error()` is empty.
int regular_value = foo_may_error() ?? -1;
io::printfn("The value was: %d", regular_value);
}
Modifying the returned Excuse
A common use of ??
is to catch an empty Optional and change
the Excuse
to another more specific Excuse
, which
allows us to distinguish one failure from the other,
even when they had the same Excuse
originally.
import std::io;
fault NoHomework
{
DOG_ATE_MY_HOMEWORK,
MY_TEXTBOOK_CAUGHT_FIRE,
DISTRACTED_BY_CAT_PICTURES
}
fn int! test()
{
return IoError.FILE_NOT_FOUND?;
}
fn void! examples()
{
int! a = test(); // IoError.FILE_NOT_FOUND
int! b = test(); // IoError.FILE_NOT_FOUND
// We can tell these apart by default assigning our own unique
// Excuse. Our custom Excuse is assigned only if an
// empty Optional is returned.
int! c = test() ?? NoHomework.DOG_ATE_MY_HOMEWORK?;
int! d = test() ?? NoHomework.DISTRACTED_BY_CAT_PICTURES?;
// If you want to immediately return with an Excuse,
// use the "?" and "!" operators together, see the code below:
int! e = test() ?? NoHomework.DOG_ATE_MY_HOMEWORK?!;
int! f = test() ?? NoHomework.DISTRACTED_BY_CAT_PICTURES?!;
}
Force unwrapping expressions
The force unwrap operator !!
will
make the program panic and exit if the expression is an empty optional.
This is useful when the error should – in normal cases – not happen
and you don’t want to write any error handling for it.
That said, it should be used with great caution in production code.
fn void find_file_and_test()
{
find_file()!!;
// Force unwrap '!!' is roughly equal to:
// if (catch find_file()) unreachable("Unexpected excuse");
}
Find empty Optional without reading the Excuse
import std::io;
fn void test()
{
int! optional_value = IoError.FILE_NOT_FOUND?;
// Find empty Optional, then handle inside scope
if (catch optional_value)
{
io::printn("Found empty Optional, the Excuse was not read");
}
}
Find empty Optional and switch on Excuse
if (catch)
can also immediately switch on the Excuse
value:
fn void! test()
{
if (catch excuse = optional_value)
{
case NoHomework.DOG_ATE_MY_HOMEWORK:
io::printn("Dog ate your file");
case IoError.FILE_NOT_FOUND:
io::printn("File not found");
default:
io::printfn("Unexpected Excuse: %s", excuse);
return excuse?;
}
}
Which is shorthand for:
fn void! test()
{
if (catch excuse = optional_value)
{
switch (excuse)
{
case NoHomework.DOG_ATE_MY_HOMEWORK:
io::printn("Dog ate your file");
case IoError.FILE_NOT_FOUND:
io::printn("File not found");
default:
io::printfn("Unexpected Excuse: %s", excuse);
return excuse?;
}
}
}
Run code if the Optional has a result
This is a convenience method, the logical inverse of
if (catch)
and is helpful when you don’t care about the empty branch of
the code or you wish to perform an early return.
fn void test()
{
// 'optional_value' is a non-Optional variable inside the scope
if (try optional_value)
{
io::printfn("Result found: %s", optional_value);
}
// The Optional result is assigned to 'unwrapped_value' inside the scope
if (try unwrapped_value = optional_value)
{
io::printfn("Result found: %s", unwrapped_value);
}
}
Another example:
import std::io;
// Returns Optional result with `int` type or empty with an Excuse
fn int! reliable_function()
{
return 7; // Return a result
}
fn void main(String[] args)
{
int! reliable_result = reliable_function();
// Unwrap the result from reliable_result
if (try reliable_result)
{
// reliable_result is unwrapped in this scope, can be used as normal
io::printfn("reliable_result: %s", reliable_result);
}
}
It is possible to add conditions to an if (try)
but they must be
joined with &&
. However you cannot use logical OR (||
) conditions:
import std::io;
// Returns Optional with an 'int' result or empty with an Excuse
fn int! reliable_function()
{
return 7; // Return an Optional result
}
fn void main(String[] args)
{
int! reliable_result1 = reliable_function();
int! reliable_result2 = reliable_function();
// Unwrap the result from reliable_result1 and reliable_result2
if (try reliable_result1 && try reliable_result2 && 5 > 2)
{
// `reliable_result1` is can be used as a normal variable here
io::printfn("reliable_result1: %s", reliable_result1);
// `reliable_result2` is can be used as a normal variable here
io::printfn("reliable_result2: %s", reliable_result2);
}
// ERROR cannot use logical OR `||`
// if (try reliable_result1 || try reliable_result2)
// {
// io::printn("this can never happen);
// }
}
Shorthands to work with Optionals
Getting the Excuse
Retrieving the Excuse
with if (catch excuse = optional_value) {...}
is not the only way to get the Excuse
from an Optional, we can use the macro @catch
instead.
Unlike if (catch)
this will never cause automatic unwrapping.
fn void main(String[] args)
{
int! optional_value = IoError.FILE_NOT_FOUND?;
anyfault excuse = @catch(optional_value);
if (excuse)
{
io::printfn("Excuse found: %s", excuse);
}
}
Checking if an Optional has a result without unwrapping
The @ok
macro will return true
if an Optional result is present and
false
if the Optional is empty.
Functionally this is equivalent to !@catch
, meaning no Excuse was found, for example:
fn void main(String[] args)
{
int! optional_value = 7;
bool result_found = @ok(optional_value);
assert(result_found == !@catch(optional_value));
}
No void! variables
The void!
type has no possible representation as a variable, and may
only be a function return type.
To store the Excuse
returned from a void!
function without
if (catch foo = optional_value)
,
use the @catch
macro to convert the Optional to an anyfault
:
fn void! test()
{
return IoError.FILE_NOT_FOUND?;
}
anyfault excuse = @catch(test());
title: Defer and Cleanup description: Defer and Cleanup sidebar:
order: 66
Defer
A defer
always runs at the end of a scope at any point after it is declared, defer
is commonly used to simplify code that needs clean-up; like closing
unix file descriptors, freeing dynamically allocated memory or closing
database connections.
End of a scope
The end of a scope also includes return
, break
, continue
or rethrow !
.
fn void test()
{
io::printn("print first");
defer io::printn("print third, on function return");
io::printn("print second");
return;
}
The defer
runs after the other print statements, at the function return.
:::note
Rethrow !
unwraps the Optional result if present, afterwards the previously
Optional variable is a normal variable again, if the Optional result is
empty then the Excuse is returned from the function back to the caller.
:::
Defer Execution order
When there are multiple defer
statements they are executed in reverse order of their declaration, last-to-first declared.
fn void test()
{
io::printn("print first");
defer io::printn("print third, defers execute in reverse order");
defer io::printn("print second, defers execute in reverse order");
return;
}
Example defer
import std::io;
fn char[]! file_read(String filename, char[] buffer)
{
// return Excuse if failed to open file
File file = file::open(filename, "r")!;
defer {
io::printn("File was found, close the file");
if (catch excuse = file.close())
{
io::printfn("Fault closing file: %s", excuse);
}
}
// return if fault reading the file into the buffer
file.read(buffer)!;
return buffer;
}
If the file named filename
is found the function will read the content into a buffer,
defer
will then make sure that any open File
handlers are closed.
Note that if a scope exit happens before the defer
declaration, the defer
will not run, this a useful property because if the file failed to open, we don’t need to close it.
Defer try
A defer try
is called at end of a scope when the returned Optional contained a result value.
Examples
fn void! test()
{
defer try io::printn("✅ defer try run");
// Returned an Optional result
return;
}
fn void main(String[] args)
{
(void)test();
}
Function returns an Optional result value,
this means defer try
runs on scope exit.
fn void! test()
{
defer try io::printn("❌ defer try not run");
// Returned an Optional Excuse
return IoError.FILE_NOT_FOUND?;
}
fn void main(String[] args)
{
if (catch err = test())
{
io::printfn("test() returned a fault: %s", err);
}
}
Function returns an Optional Excuse,
this means the defer try
does not run on scope exit.
Defer catch
A defer catch
is called at end of a scope when exiting exiting with an
Optional Excuse, and is helpful for logging, cleanup and freeing resources.
defer catch { ... }
defer (catch err) { ... };
When the fault is captured this is convenient for logging the fault:
defer (catch err) io::printfn("fault found: %s", err)
Memory allocation example
import std::core::mem;
fn char[]! test()
{
char[] data = mem::new_array(char, 12)!;
defer (catch err)
{
io::printfn("Excuse found: %s", err)
(void)free(data);
}
// Returns Excuse, memory gets freed
return IoError.FILE_NOT_FOUND?;
}
Pitfalls with defer and defer catch
If cleaning up memory allocations or resources make sure the defer
or defer catch
are declared as close to the resource declaration as possible.
This helps to avoid unwanted memory leaks or unwanted resource usage from other code rethrowing !
before the defer catch
was even declared.
fn void! function_throws()
{
return IoError.FILE_NOT_FOUND?;
}
fn String! test()
{
char[] data = mem::new_array(char, 12)!;
// ❌ Before the defer catch declaration
// memory was NOT freed
// function_throws()!;
defer (catch err)
{
io::printn("freeing memory");
(void)free(data);
}
// ✅ After the defer catch declaration
// memory freed correctly
function_throws()!;
return (String)data;
}
title: Contracts description: Contracts sidebar:
order: 67
Contracts are optional pre- and post-conditions checks that the compiler may use for optimization and runtime checks. Note that compilers are not obliged to process pre- and post-conditions at all. However, violating either pre- or post-conditions is considered undefined behaviour, so a compiler may optimize as if they always hold – even if a potential bug may cause them to be violated.
Pre-conditions
Pre-conditions are usually used to validate incoming arguments.
Each condition must be an expression that can be evaluated to a boolean.
Pre-conditions use the @require
annotation, and optionally can have an
error message to display after them.
<*
@require foo > 0, foo < 1000, "optional error msg"
*>
fn int testFoo(int foo)
{
return foo * 10;
}
Post conditions
Post conditions are evaluated to make checks on the resulting state after passing through the function.
The post condition uses the @ensure
annotation. Where return
is used to represent the return value from the function.
<*
@require foo != null
@ensure return > foo.x
*>
fn uint checkFoo(Foo* foo)
{
uint y = abs(foo.x) + 1;
// If we had row: foo.x = 0, then this would be a compile time error.
return y * abs(foo.x);
}
Parameter annotations
@param
supports [in]
[out]
and [inout]
. These are only applicable
for pointer arguments. [in]
disallows writing to the variable,
[out]
disallows reading from the variable. Without an annotation,
pointers may both be read from and written to without checks. If an &
is placed
in front of the annotation (e.g. [&in]
), then this means the pointer must be non-null
and is checked for null
.
Type | readable? | writable? | use as “in”? | use as “out”? | use as “inout” |
---|---|---|---|---|---|
no annotation | Yes | Yes | Yes | Yes | Yes |
in |
Yes | No | Yes | No | No |
out |
No | Yes | No | Yes | No |
inout |
Yes | Yes | Yes | Yes | Yes |
However, it should be noted that the compiler might not detect whether the annotation is correct or not! This program might compile, but will behave strangely:
fn void bad_func(int* i)
{
*i = 2;
}
<*
@param [&in] i
*>
fn void lying_func(int* i)
{
bad_func(i); // The compiler might not check this!
}
fn void test()
{
int a = 1;
lying_func(&a);
io::printf("%d", a); // Might print 1!
}
However, compilers will usually detect this:
<*
@param [&in] i
*>
fn void bad_func(int* i)
{
*i = 2; // <- Compiler error: cannot write to "in" parameter
}
Pure in detail
The pure
annotation allows a program to make assumptions in regard to how the function treats global variables.
Unlike for const
, a pure function is not allowed to call a function which is known to be impure.
However, just like for const
the compiler might not detect whether the annotation
is correct or not! This program might compile, but will behave strangely:
int i = 0;
def SecretFn = fn void();
fn void bad_func()
{
i = 2;
}
<*
@pure
*>
fn void lying_func(SecretFn f)
{
f(); // The compiler cannot reason about this!
}
fn void main()
{
i = 1;
lying_func(&bad_func);
io::printf("%d", i); // Might print 1!
}
However, compilers will usually detect this:
int i = 0;
def SecretFn = fn void();
fn void bad_func()
{
i = 2;
}
<*
@pure
*>
fn void lying_func(SecretFn f)
{
f(); // <- ERROR: Only '@pure' functions may be called.
}
fn void main()
{
i = 1;
lying_func(&bad_func);
io::printf("%d", i); // Might print 1!
}
Consequently, circumventing “pure” annotations is undefined behaviour.
Pre conditions for macros
In order to check macros, it’s often useful to use the builtin $defined
function which returns true if the code inside would pass semantic checking.
<*
@require $defined(resource.open, resource.open()), `Expected resource to have an "open" function`
@require resource != nil
@require $assignable(resource.open(), void*)
*>
macro open_resource(resource)
{
return resource.open();
}
title: Attributes description: Attributes sidebar:
order: 68
Attributes are compile-time annotations on functions, types, global constants and variables. Similar to Java annotations, a decoration may also take arguments. A attribute can also represent a bundle of attributes.
Built in attributes
@adhoc
Used for: type parameterized generic modules
Normally a parameterized generic module needs to be defined before it is used like:
module my_lib(<Type>);
struct MyType
{
Type value;
}
module my_code;
// Definition here
def MyType(<int>) = MyTypeInt;
fn void main()
{
MyType(<int>) x;
}
A type with @adhoc
can be declared parameterized, without any warning being issued, for example:
module my_lib(<Type>);
struct MyType @adhoc
{
Type value;
}
module my_code;
fn void main()
{
MyType(<int>) x;
}
@align(alignment)
Used for: struct, bitstructs, union, var, function
This attribute sets the minimum alignment for a field or a variable, for example:
struct Foo @align(32)
{
int a;
int b @align(16);
}
@benchmark
Used for: function
Marks the function as a benchmark function. Will be added to the list of benchmark functions when the benchmarks are run, otherwise the function will not be included in the compilation.
@bigendian
Used for: bitstruct
Lays out the bits as if the data was stored in a big endian type, regardless of host system endianness.
@builtin
Used for: function, macro, global, const
Allows a macro, function, global or constant be used from another module without the module path prefixed. Should be used sparingly.
@callc
Used for: function
Sets the call convention, which may be ignored if the convention is not supported on the target.
Valid arguments are veccall
, ccall
, stdcall
.
@deprecated
Used for: types, function, macro, global, const, member
Marks the particular type, global, const or member as deprecated, making use trigger a warning.
@export
Used for: function, global, const, enum, union, struct, fault
Marks this declaration as an export, this ensures it is never removed and exposes it as public when linking.
The attribute takes an optional string value, which is the external name. This acts as if @extern
had been
added with that name.
@extern
Used for: function, global, const, enum, union, struct, fault
Sets the external (linkage) name of this declaration.
@finalizer
Used for: function
Make this function run at shutdown. See @init
for the optional priority. Note that running a
finalizer is a “best effort” attempt by the OS. During abnormal termination it is not guaranteed to run.
The function must be a void function taking no arguments.
@if
Used for: all declarations
Conditionally includes the declaration in the compilation. It takes a constant compile time value argument, if this
value is true
then the declaration is retained, on false it is removed.
@init
Used for: function
Make this function run at startup before main. It has an optional priority 1 - 65535, with lower being executed earlier. It is not recommended to use values less than 128 as they are generally reserved and using them may interfere with standard program initialization.
The function must be a void function taking no arguments.
@inline
Used for: function, call
Declares a function to always be inlined or if placed on a call, that the call should be inlined.
@littleendian
Used for: bitstruct
Lays out the bits as if the data was stored in a little endian type, regardless of host system endianness.
@local
Used for: any declaration
Sets the visibility to “local”, which means it’s only visible in the current module section.
@maydiscard
Used for: function, macro
Allows the return value of the function or macro to be discarded even if it is an optional. Should be used sparingly.
@naked
Used for: function
This attribute disables prologue / epilogue emission for the function.
@nodiscard
Used for: function, macro
The return value may not be discarded.
@noinit
Used for: global, local variable
Prevents the compiler from zero initializing the variable.
@norecurse
Used for: import
Import the module but not sub-modules or parent-modules, see Modules Section.
@noreturn
Used for: function
Declares that the function will never return.
@nostrip
Used for: any declaration
This causes the declaration never to be stripped from the executable, even if it’s not used. This also transitively applies to any dependencies the declaration might have.
@obfuscate
Used for: any declaration
Removes any string values that would identify the declaration in some way. Mostly this is used on faults and enums to remove the stored names.
@operator
Used for: method, macro method
This attribute has arguments []
[]=
&[]
and len
allowing operator overloading for []
and foreach
.
By implementing []
and len
, foreach
and foreach_r
is enabled. In order to do foreach
by reference,
&[]
must be implemented as well.
@overlap
Used for: bitstruct
Allows bitstruct fields to have overlapping bit ranges.
@packed
Used for: struct, union
Causes all members to be packed as if they had alignment 1. The alignment of the struct/union is set to 1.
This alignment can be overridden with @align
.
@private
Used for: any declaration
Sets the visibility to “private”, which means it is visible in the same module, but not from other modules.
@pure
Used for: call
Used to annotate a non pure function as “pure” when checking for conformance to @pure
on
functions.
@packed
Used for: struct, union, enum
If used on a struct or enum: packs the type, including any components to minimum size. On an enum, it uses the smallest representation containing all its values.
@reflect
Used for: any declaration
Adds additional reflection information. Has no effect currently.
@section(name)
Used for: function, const, global
Declares that a global variable or function should appear in a specific section.
@test
Used for: function
Marks the function as a test function. Will be added to the list of test functions when the tests are run, otherwise the function will not be included in the compilation.
@unused
Used for: any declaration
Marks the declaration as possibly unused (but should not emit a warning).
@used
Used for: any declaration
Marks a parameter, value etc. as must being used.
@weak
Used for: function, const, global
Emits a weak symbol rather than a global.
User defined attributes
User defined attributes are intended for conditional application of built-in attributes.
def @MyAttribute = { @noreturn @inline };
// The following two are equivalent:
fn void foo() @MyAttribute { ... }
fn void foo() @noreturn @inline { ... }
A user defined attribute may also be completely empty:
def @MyAttributeEmpty = {};
title: C Interoperability description: C Interoperability sidebar:
order: 69
C3 is C ABI compatible. That means you can call C from C3, and call C3 from C without having to
do anything special. As a quick way to call C, you can simply declare the function as a
C3 function but with extern
in front of it. As long as the function is linked, it will work:
extern fn void puts(char*); // C "puts"
fn void main()
{
// This will call the "puts"
// function in the standard c lib.
puts("Hello, world!");
}
To use a different identifier inside of your C3 code compared to the function or variable’s external name, use the @extern attribute:
extern fn void foo_puts(char*) @extern("puts"); // C "puts"
fn void main()
{
foo_puts("Hello, world!"); // Still calls C "puts"
}
While C3 functions are available from C using their external name, it’s often useful to
define an external name using @extern
or @export
with a name to match C usage.
module foo;
fn int square(int x) @export // @export ensures external visibility
{
return x * x;
}
fn int square2(int x) @export("square")
{
return x * x;
}
Calling from C:
extern int square(int);
int foo_square(int) __attribute__ ((weak, alias ("foo.square")));
void test()
{
// This would call square2
printf("%d\n", square(11));
// This would call square
printf("%d\n", foo_square(11));
}
Linking static and dynamic libraries
If you have a library foo.a
or foo.so
or foo.obj
(depending on type and OS), just add
-l foo
on the command line, or in the project file add it to the linked-libraries
value, e.g.
"linked-libraries" = ["foo"]
.
To add library search paths, use -L <directory>
from the command line and linker-search-paths
the project file (e.g. "linker-search-paths" = ["../mylibs/", "/extra-libs/"]
)
Gotchas
- Bitstructs will be seen as its backing type, when used from C.
- C bit fields must be manually converted to a C3 bitstruct with the correct layout for each target platform.
- C assumes the enum size is
CInt
- C3 uses fixed integer sizes, this means that
int
andCInt
does not need to be the same though in practice on 32/64 bit machines,long
is usually the only type that differs in size between C and C3. - Atomic types are not supported by C3.
- In C3 there are generic Atomic types instead.
- There are no
volatile
andconst
qualifiers like in C.- C3 has global constants declared with
const
. - Instead of the
volatile
type qualifier, there are standard library macros@volatile_load
and@volatile_store
.
- C3 has global constants declared with
- Passing arrays by value like in C3 must be represented as passing a struct containing the array.
- In C3, fixed arrays do not decay into pointers like in C.
- When defining a C function that has an array argument, replace the array type with a pointer. E.g.
void test(int[] a)
should becomeextern fn void test(int* a)
. If the function has a sized array, likevoid test2(int[4] b)
replace it with a pointer to a sized array:extern fn void test2(int[4]* b);
- Note that a pointer to an array is always implicitly convertable to a pointer to the first element For example,
int[4]*
may be implicitly converted toint*
.
- When defining a C function that has an array argument, replace the array type with a pointer. E.g.
- The C3 names of functions are name-spaced with the module by default when using
@export
, so when exporting a function with@export
that is to be used from C, specify an explicit external name. E.g.fn void myfunc() @export("myfunc") { ... }
.
title: Interfaces and Any Type description: Interfaces and Any Type sidebar:
order: 80
Working with the type of any
at runtime.
The any
type is recommended for writing code that is polymorphic at runtime where macros are not appropriate.
It can be thought of as a typed void*
.
An any
can be created by assigning any pointer to it. You can then query the any
type for the typeid of
the enclosed type (the type the pointer points to) using the type
field.
This allows switching over the typeid, either using a normal switch:
switch (my_any.type)
{
case Foo.typeid:
...
case Bar.typeid:
...
}
Or the special any
-version of the switch:
switch (my_any)
{
case Foo:
// my_any can be used as if it was Foo* here
case Bar:
// my_any can be used as if it was Bar* here
}
Sometimes one needs to manually construct an any-pointer, which
is typically done using the any_make
function: any_make(ptr, type)
will create an any
pointing to ptr
and with typeid type
.
Since the runtime typeid
is available, we can query for any runtime typeid
property available
at runtime, for example the size, e.g. my_any.type.sizeof
. This allows us to do a lot of work
on with the enclosed data without knowing the details of its type.
For example, this would make a copy of the data and place it in the variable any_copy
:
void* data = malloc(a.type.sizeof);
mem::copy(data, a.ptr, a.type.sizeof);
any any_copy = any_make(data, a.type);
Variable argument functions with implicit any
Regular typed varargs are of a single type, e.g. fn void abc(int x, double... args)
.
In order to take variable functions that are of multiple types, any
may be used.
There are two variants:
Explicit any
vararg functions
This type of function has a format like fn void vaargfn(int x, any... args)
. Because only
pointers may be passed to an any
, the arguments must explicitly be pointers (e.g. vaargfn(2, &b, &&3.0)
).
While explicit, this may be somewhat less user-friendly than implicit vararg functions:
Implicit any
vararg functions
The implicit any
vararg function has instead a format like fn void vaanyfn(int x, args...)
.
Calling this function will implicitly cause taking the pointer of the values (so for
example in the call vaanyfn(2, b, 3.0)
, what is actually passed are &b
and &&3.0
).
Because this passes values implicitly by reference, care must be taken not to mutate any values passed in this manner. Doing so would very likely break user expectations.
Interfaces
Most statically typed object-oriented languages implements extensibility using vtables. In C, and by extension C3, this is possible to emulate by passing around structs containing list of function pointers in addition to the data.
While this is efficient and often the best solution, but it puts certain assumptions on the code and makes interfaces more challenging to evolve over time.
As an alternative there are languages (such as Objective-C) which instead use message passing to dynamically typed objects, where the availability of a certain functionality may be queried at runtime.
C3 provides this latter functionality over the any
type using interfaces.
Defining an interface
The first step is to define an interface:
interface MyName
{
fn String myname();
}
While myname
will behave as a method, we declare it without type. Note here that unlike normal methods we leave
out the first “self”, argument.
Implementing the interface
To declare that a type implements an interface, add it after the type name:
struct Baz (MyName)
{
int x;
}
// Note how the first argument differs from the interface.
fn String Baz.myname(Baz* self) @dynamic
{
return "I am Baz!";
}
If a type declares an interface but does not implement its methods, then that is compile time error.
A type may implement multiple interfaces, by placing them all inside of ()
e.g. struct Foo (VeryOptional, MyName) { ... }
A limitation is that only user-defined types may declare they are implementing interfaces. To make existing types implement interfaces is possible but does not provide compile time checks.
One of the interfaces available in the standard library is Printable, which contains to_format
and to_new_string
.
If we implemented it for our struct above it might look like this:
fn String Baz.to_new_string(Baz baz, Allocator allocator) @dynamic
{
return string::printf("Baz(%d)", baz.x, allocator: allocator);
}
“@dynamic” methods
A method must be declared @dynamic
to implement an interface, but a method may also be declared @dynamic
without
the type declaring it implements a particular interface. For example, this allows us to write:
// This will make "int" satisfy the MyName interface
fn String int.myname(int*) @dynamic
{
return "I am int!";
}
@dynamic
methods have their reference retained in the runtime code and can also be searched for at runtime and invoked
from the any
type.
Referring to an interface by pointer
An interface e.g. MyName
, can be cast back and forth to any
, but only types which
implement the interface completely may implicitly be cast to the interface.
So for example:
Bob b = { 1 };
double d = 0.5;
int i = 3;
MyName a = &b; // Valid, Bob implements MyName.
// MyName c = &d; // Error, double does not implement MyName.
MyName c = (MyName)&d; // Would break at runtime as double doesn't implement MyName
// MyName z = &i; // Error, implicit conversion because int doesn't explicitly implement it.
MyName* z = (MyName)&i; // Explicit conversion works and is safe at runtime if int implements "myname"
Calling dynamic methods
Methods implementing interfaces are like normal methods, and if called directly, they are just normal function calls. The difference is that they may be invoked through the interface:
fn void whoareyou(MyName a)
{
io::printn(a.myname());
}
If we have an optional method we should first check that it is implemented:
fn void do_something(VeryOptional z)
{
if (&z.do_something)
{
z.do_something(1, null);
}
}
We first query if the method exists on the value. If it does we actually run it.
Here is another example, showing how the correct function will be called depending on type, checking
for methods on an any
:
fn void whoareyou2(any a)
{
// Query if the function exists
if (!&a.myname)
{
io::printn("I don't know who I am.");
return;
}
// Dynamically call the function
io::printn(((MyName)a).myname());
}
fn void main()
{
int i;
double d;
Bob bob;
any a = &i;
whoareyou2(a); // Prints "I am int!"
a = &d;
whoareyou2(a); // Prints "I don't know who I am."
a = &bob;
whoareyou2(a); // Prints "I am Bob!"
}
Reflection invocation
This functionality is not yet implemented and may see syntax changes
It is possible to retrieve any @dynamic
function by name and invoke it:
def VoidMethodFn = fn void(void*);
fn void* int.test_something(&self) @dynamic
{
io::printfn("Testing: %d", *self);
}
fn void main()
{
int z = 321;
any a = &z;
VoidMethodFn test_func = a.reflect("test_something");
test_func(a); // Will print "Testing: 321"
}
This feature allows methods to be linked up at runtime.
title: Operator Overloading description: Operator Overloading sidebar:
order: 81
C3 allows some limited operator overloading for working with containers.
“Element at” operator []
Implementing []
allows a type to use the my_type[<value>]
syntax:
struct Foo
{
double[] x;
}
fn double Foo.get(&self, usz i) @operator([])
{
return self.x[i];
}
It’s possible to use any type as argument, such as a string:
fn double Bar.get(&self, String str) @operator([])
{
return self.get_val_by_key(str);
}
Only a single [] overload is allowed.
“Element ref” operator &[]
Similar to [], the operator returns a value for &my_type[<value>]
, which may
be retrieved in a different way. If this overload isn’t defined, then &my_type[<value>]
would
be a syntax error.
fn double* Foo.get_ref(&self, usz i) @operator(&[])
{
return &self.x[i];
}
“Element set” operator []=
The counterpart of [] allows setting an element using my_type[<index>] = <value>
.
fn void Foo.set(&self, usz i, double new_val) @operator([]=)
{
return self.x[i] = new_val;
}
“len” operator
Unlike the previous operator overloads, the “len” operator simply enables functionality
which augments the []
-family of operators: you can use the “from end” syntax e.g my_type[^1]
to get the last element assuming the indexing uses integers.
Enabling ‘foreach’
In order to use a type with foreach, e.g. foreach(d : foo)
, at a minimum methods
with overloads for []
(@operator([])
) and len
(@operator(len)
) need to be added.
If &[]
is implemented, foreach by reference is enabled (e.g. foreach(double* &d : foo)
)
fn double Foo.get(&self, usz i) @operator([])
{
return self.x[i];
}
fn usz Foo.len(&self) @operator(len)
{
return self.x.len;
}
fn void test(Foo f)
{
// Print all elements in f
foreach (d : f)
{
io::printfn("%f", d);
}
}
:::note
Operator overloading is limited, by design, as these features delivered the most value while still keeping the language as simple as possible.
:::
title: Generics description: Generics sidebar:
order: 82
Generic modules are parameterized modules that allow functionality for arbitrary types.
For generic modules, the generic parameters follows the module name:
// TypeA, TypeB, TypeC are generic parameters.
module vector(<TypeA, TypeB, TypeC>);
It is also possible to parameterize by an int
or bool
constant, for example:
// module custom_type<Type, VALUE>
module custom_type<float, 3>;
Code inside a generic module may use the generic parameters as if they were well-defined symbols:
module foo_test(<Type1, Type2>);
struct Foo
{
Type1 a;
}
fn Type2 test(Type2 b, Foo *foo)
{
return foo.a + b;
}
Including a generic module works as usual:
import foo_test;
def FooFloat = Foo(<float, double>);
def test_float = foo_test::test(<float, double>);
...
FooFloat f;
Foo(<int, double>) g;
...
test_float(1.0, &f);
foo_test::test(<int, double>)(1.0, &g);
Just like for macros, optional constraints may be added to improve compile errors:
<*
@require $assignable(1, TypeB) && $assignable(1, TypeC)
@require $assignable((TypeB)1, TypeA) && $assignable((TypeC)1, TypeA)
*>
module vector(<TypeA, TypeB, TypeC>);
/* .. code * ../
def testFunction = vector::testFunc(<Bar, float, int>);
// This would give the error
// --> Parameter(s) failed validation:
// @require "$assignable((TypeB)1, TypeA) && $assignable((TypeC)1, TypeA)" violated.
title: Macros description: Macros sidebar:
order: 83
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
Conditional compilation
// C Macro
#if defined(x) && Y > 3
int z;
#endif
// C3 Macro
$if $defined(x) && Y > 3:
int z;
$endif
// or
int z @if($defined(x) && Y > 3);
Macros
// C Macro
#define M(x) ((x) + 2)
#define UInt32 unsigned int
// Use:
int y = M(foo() + 2);
UInt32 b = y;
// C3 Macro
macro m(x)
{
return x + 2;
}
def UInt32 = uint;
// Use:
int y = m(foo() + 2);
UInt32 b = y;
Dynamic scoping
// C Macro
#define Z() ptr->x->y->z
int x = Z();
// C3 Macro
... currently no corresponding functionality ...
Expression arguments
// C Macro
#define M(x, y) x = 2 * (y);
...
M(x, 3);
// C3 Macro
macro @m(#x, y)
{
#x = 2 * y;
}
...
@m(x, 3);
First class types
// C Macro
#define SIZE(T) (sizeof(T) + sizeof(int))
// C3 Macro
macro size($Type)
{
return $Type.sizeof + int.sizeof;
}
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 Macro
macro @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
// C Macro
#define offsetof(T, field) (size_t)(&((T*)0)->field)
// C3 Macro
macro usz @offset($Type, #field)
{
$Type* t = null;
return (usz)(uptr)&t.#field;
}
Declaration attributes
// C Macro
#define PURE_INLINE __attribute__((pure)) __attribute__((always_inline))
int foo(int x) PURE_INLINE { ... }
// C3 Macro
def @NoDiscardInline = { @nodiscard @inline };
fn int foo(int) @NoDiscardInline { ... }
Declaration macros
// C Macro
#define DECLARE_LIST(name) List name = { .head = NULL };
// Use:
DECLARE_LIST(hello)
// C3 Macro
... currently no corresponding functionality ...
Stringification
// C Macro
#define CHECK(x) do { if (!x) abort(#x); } while(0)
// C3 Macro
macro @check(#expr)
{
if (!#expr) abort($stringify(#expr));
}
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. @
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
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
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()
{
int[] a = { 1, 2, 3 };
{
int[] __a = a;
for (int __i = 0; i < __a.len; i++)
{
io::printfn("Value: %d, x2: %d", __value1, __value2);
}
}
}
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
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
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
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
$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:
int[*] a = { 5, $vasplat[2..], 77 };
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);
$endforeach
int[2] x = $a;
io::printfn("%s", x);
io::printfn("%s", $a[1]);
// Will print
// 1
// 2
// [1, 2]
// 2
title: Compile Time Evaluation description: Compile time introspection and execution sidebar:
order: 84
During compilation, constant expressions will automatically be folded. Together with the compile
time conditional statements $if
, $switch
and the compile time iteration statements $for
$foreach
it is possible to perform limited compile time execution.
Compile time values
During compilation, global constants are considered compile time values, as are any derived constant values, such as type names and sizes, variable alignments etc.
Inside of a macro or a function, it is possible to define mutable compile time variables. Such
local variables are prefixed with $
(e.g. $foo
). It is also possible to define local type variables,
that are also prefixed using $
(e.g. $MyType
$ParamType
).
Mutable compile time variables are not allowed in the global scope.
$if and $switch
$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;
}
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;
$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;
$endswitch
}
Loops using $foreach and $for
$for
… $endfor
works analogous to for
, only it is limited to using compile time variables. $foreach
… $endforeach
similarly
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);
}
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);
}
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.
Compile time macro execution
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;
}
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
}
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.
This limitation is deliberate, to offer control over what is compiled out and what isn’t.
Conditional compilation at the top level using @if
At the top level, conditional compilation is controlled using with @if
attributes on declarations
fn void foo_win32() @if(env::WIN32)
{
/* .... */
}
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
Evaluation order of top level conditional compilation
Conditional compilation at the top level can cause unexpected ordering issues, especially when combined with
$defined
. At a high level, there are three phases of evaluation:
- Non-conditional declarations are registered.
- Conditional module sections are either discarded or have all of their non-conditional declarations registered.
- Each module in turn will evaluate
@if
attributes for each module section.
The order of module and module section evaluation in (2) and (3) is not deterministic and any use of $defined
should not
rely on this ordering.
Compile time introspection
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;
To read more about all the fields available at compile time, see the page on reflection.
Compile time functions
A set of compile time functions are available at compile time:
$alignof
Get the alignment of something. See reflection.
$append
Append a compile time constant to a compile time array or untyped list.
$assert
Check a condition at compile time.
$assignable
Check if an expression is assignable to the given type, e.g. Type x = expr;
would be valid.
$defined
Returns true if a type or identifier is defined. See reflection.
$echo
Print a message to stdout when compiling the code.
$embed
Embed binary data from a file. See expressions.
$error
When this is compiled, issue a compile time error.
$eval
Converts a compile time string to the corresponding variable or function. See reflection.
$evaltype
Converts a compile time string to the corresponding type. See reflection.
$exec
Execute a script at compile time and include the result in the source code.
$extnameof
, $qnameof
and $nameof
Get the external name of a symbol. See reflection.
$feature
Check if a given feature is enabled.
$is_const
Check if the expression is constant at compile time.
$nameof
Get the local name of a symbol. See reflection.
$offsetof
Get the offset of a member. See reflection.
$qnameof
Get the qualified name of a symbol. See reflection.
$vacount
Return the number of macro vaarg arguments
$vaconst
Return a vaarg as a $constant
parameter.
$vaexpr
Return a vaarg as an #expr
parameter.
$vasplat
Expand the vaargs in an initializer list or function call.
$vatype
Get a vaarg as a $Type
parameter.
$sizeof
Return the size of an expression.
$stringify
Turn an expression into a string.
$typeof
Get the type of an expression (without evaluating it).
$typefrom
Get a type from a compile time constant typeid
.
title: Reflection description: Reflection sidebar:
order: 85
C3 allows both compile time and runtime reflection.
During compile time the type information may be directly used as compile time constants, the same data is then available dynamically at runtime.
Note that not all reflection is implemented in the compiler at this point in time.
Compile time reflection
During compile time there are a number of compile time fields that may be accessed directly.
Type properties
It is possible to access properties on the type itself:
alignof
associated
elements
extnameof
inf
inner
kindof
len
max
membersof
min
nan
nameof
names
params
parentof
qnameof
returns
sizeof
typeid
values
alignof
Returns the alignment in bytes needed for the type.
struct Foo @align(8)
{
int a;
}
uint a = Foo.alignof; // 8
associated
Only available for enums. Returns an array containing the types of associated values if any.
enum Foo : int (double d, String s)
{
BAR = { 1.0, "normal" },
BAZ = { 2.0, "exceptional" }
}
String s = Foo.associated[0].nameof; // "double"
elements
Returns the element count of an enum or fault.
enum FooEnum
{
BAR,
BAZ
}
int x = FooEnum.elements; // 2
inf
Only available for floating point types
Returns a representation of floating point “infinity”.
inner
This returns a typeid to an “inner” type. What this means is different for each type:
- Array -> the array base type.
- Bitstruct -> underlying base type.
- Distinct -> the underlying type.
- Enum -> underlying enum base type.
- Pointer -> the type being pointed to.
- Vector -> the vector base type.
It is not defined for other types.
kindof
Returns the underlying TypeKind
as defined in std::core::types.
TypeKind kind = int.kindof; // TypeKind.SIGNED_INT
len
Returns the length of the array.
usz len = int[4].len; // 4
max
Returns the maximum value of the type (only valid for integer and float types).
ushort max_ushort = ushort.max; // 65535
membersof
Only available for bitstruct, struct and union types.
Returns a compile time list containing the fields in a bitstruct, struct or union. The
elements have the compile time only type of member_ref
.
Note: As the list is an “untyped” list, you are limited to iterating and accessing it at compile time.
struct Baz
{
int x;
Foo* z;
}
String x = Baz.membersof[1].nameof; // "z"
A member_ref
has properties alignof
, kindof
, membersof
, nameof
, offsetof
, sizeof
and typeid
.
min
Returns the minimum value of the type (only valid for integer and float types).
ichar min_ichar = ichar.min; // -128
nameof
Returns the name of the type.
names
Returns a slice containing the names of an enum or fault.
enum FooEnum
{
BAR,
BAZ
}
String[] x = FooEnum.names; // ["BAR", "BAZ"]
paramsof
Only available for function pointer types. Returns a ReflectParam struct for all function pointer parameters.
def TestFunc = fn int(int x, double f);
String s = TestFunc.paramsof[1].name; // "f"
typeid t = TestFunc.paramsof[1].type; // double.typeid
parentof
Only available for bitstruct and struct types. Returns the typeid of the parent type.
struct Foo
{
int a;
}
struct Bar
{
inline Foo f;
}
String x = Bar.parentof.nameof; // "Foo"
returns
Only available for function types. Returns the typeid of the return type.
def TestFunc = fn int(int, double);
String s = TestFunc.returns.nameof; // "int"
sizeof
Returns the size in bytes for the given type, like C sizeof
.
usz x = Foo.sizeof;
typeid
Returns the typeid for the given type. def
s will return the typeid of the underlying type. The typeid size is the same as that of an iptr
.
typeid x = Foo.typeid;
values
Returns a slice containing the values of an enum or fault.
enum FooEnum
{
BAR,
BAZ
}
String x = FooEnum.values[1].nameof; // "BAR"
Compile time functions
There are several built-in functions to inspect the code during compile time.
$alignof
$defined
$eval
$evaltype
$extnameof
$nameof
$offsetof
$qnameof
$sizeof
$stringify
$typeof
$alignof
Returns the alignment in bytes needed for the type or member.
module test::bar;
struct Foo
{
int x;
char[] y;
}
int g = 123;
$alignof(Foo.x); // => returns 4
$alignof(Foo.y); // => returns 8 on 64 bit
$alignof(Foo); // => returns 8 on 64 bit
$alignof(g); // => returns 4
$defined
Returns true if the expression inside is defined and all sub expressions are valid.
$defined(Foo.x); // => returns true
$defined(Foo.z); // => returns false
int[2] abc;
$defined(abc.len); // => returns true
$defined(abc.len()); // => returns false
$defined((int)abc); // => returns false
// $defined(abc.len() + 1) would be an error
$eval
Converts a compile time string with the corresponding variable:
int a = 123; // => a is now 123
$eval("a") = 222; // => a is now 222
$eval("mymodule::fooFunc")(a); // => same as mymodule::fooFunc(a)
$eval
is limited to a single, optionally path prefixed, identifier.
Consequently methods cannot be evaluated directly:
struct Foo { ... }
fn int Foo.test(Foo* f) { ... }
fn void test()
{
void* test1 = &$eval("test"); // Works
void* test2 = &Foo.$eval("test"); // Works
// void* test3 = &$eval("Foo.test"); // Error
}
$evaltype
Similar to $eval
but for types:
$evaltype("float") f = 12.0f;
$extnameof
Returns the external name of a type, variable or function. The external name is the one used by the linker.
fn void testfn(int x) { }
String a = $extnameof(g); // => "test.bar.g";
string b = $extnameof(testfn); // => "test.bar.testfn"
$nameof
Returns the name of a function or variable as a string without module prefixes.
fn void test() { }
int g = 1;
String a = $nameof(g); // => "g"
String b = $nameof(test); // => "test"
$offsetof
Returns the offset of a member in a struct.
Foo z;
$offsetof(z.y); // => returns 8 on 64 bit, 4 on 32 bit
$qnameof
Returns the same as $nameof
, but with the full module name prepended.
module abc;
fn void test() { }
int g = 1;
String a = $qnameof(g); // => "abc::g"
String b = $qnameof(test); // => "abc::test"
$sizeof
This is used on a value to determine the allocation size needed. $sizeof(a)
is equivalent
to doing $typeof(a).sizeof
. Note that this is only used on values and not on types.
$typeof(a)* x = allocate_bytes($sizeof(a));
*x = a;
$stringify
Returns the expression as a string. It has a special behaviour for macro expression parameters,
where $stringify(#foo)
will return the expression contained in #foo
rather than simply return
“#foo”
$typeof
Returns the type of an expression or variable as a type itself.
Foo f;
$typeof(f) x = f;
title: Standard Library description: Standard Library sidebar:
order: 128
The standard library is currently in development, so frequent changes will occur. Note that all std::core modules and sub modules are implicitly imported.
std::core::builtin
All functions and macros in this library can be used without path qualifiers.
void panic(char message, char file, char *function, uint line)
Default function called when the asserts fails.
void @swap(&a, &b)
Swap values in a
and b
.
int a = 3;
int b = 5;
@swap(a, b);
io::printfn("%d", a); // Prints 5
anycast(any v, $Type)
Optionally cast the value v
to type $Type*
on failure returns CastResult.TYPE_MISMATCH
.
int b;
any a = &b;
float*! c = anycast(a, float); // Will return TYPE_MISMATCH
int*! d = anycast(a, int); // Works!
void unreachable($string = “Unreachable statement reached.”)
Mark a code path as unreachable.
switch (x)
{
case 0:
foo();
case 1:
bar();
default:
// Should never happen.
unreachable("x should have been 0 or 1");
}
On safe mode this will throw a runtime panic when reached. For release mode the compiler will assume this case never happens.
bitcast(value, $Type)
Do a bitcast of a value to $Type
, requires that the types are of the same memory size.
float x = 1.0;
int y = bitcast(x, int); // y = 0x3f800000
enum_by_name($Type, enum_name)
Optionally returns the enum value with the given name. $Type
must be an enum. Returns SearchResult.MISSING
on failure.
enum Foo { ABC, CDE, EFG }
fn void! test()
{
Foo f = enum_by_name(Foo, "CDE")!;
// same as Foo f = Foo.CDE;
}
void @scope(&variable; @body)
Scopes a variable:
int a = 3;
@scope(a)
{
a = 4;
a++;
};
// Prints a = 3
io::printfn("a = %d", a, b);
less, greater, less_eq, greater_eq, equals
All macros take two values and compare them. Any type implementing Type.less
or Type.compare_to
may be compared (or if the type implements <
). Types
implementing Type.equals
may use equals
even if neither less
nor compare_to
are implemented.
Faults
IteratorResult
returned when reaching the end of an iterator.SearchResult
used when a search fails.CastResult
when an anycast fails.
std::core::env
Constants
OS_TYPE
the OS type compiled for.COMPILER_OPT_LEVEL
the optimization level used.I128_SUPPORT
true if int128 support is available.COMPILER_SAFE_MODE
true if compiled with safety checks.
std::core::mem
malloc, malloc_checked, malloc_aligned
Allocate the given number of bytes. malloc
will panic on out of memory,
whereas malloc_checked
and malloc_aligned
returns an optional value.
malloc_aligned
adds an alignment, which must be a power of 2. Any pointer
allocated using malloc_aligned
must be freed using free_aligned
rather
the normal free
or memory corruption may result.
char* data = malloc(8);
char*! data2 = malloc_checked(8);
int[<16>]*! data3 = malloc_aligned(16 * int.sizeof), 128);
new($Type, #initializer), new_aligned($Type, #initializer)
This allocates a single element of $Type, returning the pointer. An optional initializer may be added, which immediately initializes the value to that of the initializer.
If no initializer is provided, it is zero initialized. new_aligned
works the same but for overaligned types, such allocations
must be freed using free_aligned
int* a = mem::new(int);
Foo* foo = mem::new(Foo, { 1, 2 });
alloc($Type), alloc_aligned($Type)
Allocates a single element of $Type, same as new
, but without initializing the data.
new_array($Type, usz elements), new_array_aligned($Type, usz elements)
Allocates a slice of elements
number of elements, returning
a slice of the given length. Elements are zero initialized. new_array_aligned
is used for
types that exceed standard alignment.
int[] ints = mem::new_array(int, 100); // Allocated int[100] on the heap, zero initialized.
alloc_array($Type, usz elements), alloc_array_aligned($Type, usz elements)
Same as new_array
but without initialization.
calloc, calloc_checked, calloc_aligned
Identical to the malloc
variants, except the data is guaranteed to be zeroed out.
relloc, relloc_checked, realloc_aligned
Resizes memory allocated using malloc
or calloc
. Any extra data is
guaranteed to be zeroed out. realloc_aligned
can only be used with
pointers created using calloc_aligned
or alloc_aligned
.
free, free_aligned
Frees memory allocated using malloc
or calloc
. Any memory allocated using “_aligned” variants
must be freed using free_aligned
.
@scoped(Allocator* allocator; @body())
Swaps the current memory allocator for the duration of the call.
DynamicArenaAllocator dynamic_arena;
dynamic_arena.init(1024);
mem::@scoped(&dynamic_arena)
{
// This allocation uses the dynamic arena
Foo* f = malloc(Foo);
};
// Release any dynamic arena memory.
dynamic_arena.destroy();
@tscoped(; @body())
Same as @scoped, but uses the temporary allocator rather than any arbitrary allocator.
void* tmalloc(usz size, usz alignment = 0)
Allocates memory using the temporary allocator. Panic on failure. It has type
variants similar to malloc
, so tmalloc(Type)
would create a Type*
using
the temporary allocator.
void* tcalloc(usz size, usz alignment = 0)
Same as tmalloc
but clears the memory.
void trealloc(void ptr, usz size, usz alignment = 0)
realloc
but on memory received using tcalloc
or tmalloc
.
temp_new, temp_alloc, temp_new_array, temp_alloc_array
Same as the new
, alloc
, new_array
and alloc_array
respectively.
void @pool(;@body)
Opens a temporary memory scope.
@pool()
{
// This allocation uses the dynamic arena
Foo* f = tmalloc(Foo);
};
@volatile_load(&x)
Returns the value in x
using a volatile load.
// Both loads will always happen:
int y = @volatile_load(my_global);
y = @volatile_load(my_global);
@volatile_store(&x, y)
Store the value y
in x
using a volatile store.
// Both stores will always happen:
@volatile_store(y, 1);
@volatile_store(y, 1);
usz aligned_offset(usz offset, usz alignment)
Returns an aligned size based on the current offset. The alignment
must be a power of two. E.g. mem::aligned_offset(17, 8)
would return 24
usz aligned_pointer(void* ptr, usz alignment)
Returns a pointer aligned to the given alignment, using aligned_offset
.
bool ptr_is_aligned(void* ptr, usz alignment)
Return true if the pointer is aligned, false otherwise.
void copy(void dst, void src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
Copies bytes from one pointer to another. It may optionally be set as volatile, in which case the copy may not be optimized away. Furthermore the source and destination alignment may be used.
Foo* f = tmalloc(data_size);
mem::copy(f, slice.ptr, size);
void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false)
Sets bytes to a value. This operation may be aligned and/or volatile. See the copy
method.
void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false)
Sets bytes to zero. This operation may be aligned and/or volatile. See the copy
method.
@clone(&value)
Makes a shallow copy of a value using the regular allocator.
Foo f = ...
return @clone(f);
@tclone(&value)
Same as @clone
but uses the temporary allocator.
std::core::types
bool is_comparable_value($Type)
Return true if the type can be used with comparison operators.
bool is_equatable_value(value)
Return true
if the value can be compared using the equals
macro.
bool is_equatable_value(value)
Return true
if the value can be compared using the comparison macros.
kind_is_int(TypeKind kind)
any_to_int(any* v, $Type)
Returns an optional value of $Type
if the any value losslessly
may be converted into the given type. Returns a ConversionResult
otherwise.
any* v = &&128;
short y = any_to_int(v, short)!!; // Works
ichar z = any_to_int(v, ichar)!!; // Panics VALUE_OUT_OF_RANGE
std::core::str::conv
usz! char32_to_utf8(Char32 c, char* output, usz available)
Convert a UTF32 codepoint to an UTF8 buffer. size
has the number of
writable bytes left. It returns the number of bytes used, or
UnicodeResult.CONVERSION_FAILED
if the buffer is too small.
void char32_to_utf16_unsafe(Char32 c, Char16** output)
Convert a UTF32 codepoint to an UTF16 buffer without bounds checking, moving the output pointer 1 or 2 steps.
std::io
String! readline(stream = io::stdin(), Allocator allocator = allocator::heap())
Read a String!
from a file stream, which is standard input (stdin) by default, reads to the next newline character \n
or to the end of stream. Readline
returns an Optional string.
import std::io;
fn void! hello_name()
{
String! name = io::readline();
if (catch excuse = name)
{
return excuse?;
}
io::printfn("Name was: %s.", name);
}
:::Note
`\r` will be filtered from the String.
:::
### String! treadline(stream = io::stdin())
Read a `String!` from a file stream which is standard input (stdin) by default, Reads to the next newline character `\n` or to the end of stream. `Treadline` returns an [Optional](/language-common/optionals-essential/#what-is-an-optional) string. The temporary allocator is used by `Treadline`, in contrast the `readline` defaults to the heap allocator, but is configurable to other allocators.
```c
import std::io;
fn void! hello_name()
{
String! name = io::treadline();
if (catch excuse = name) {
return excuse?;
}
io::printfn("Hello %s! Hope you have a great day", name);
}
:::Note
\r
will be filtered from the String.
:::
void print(x), void printn(x = “”)
Print a value to stdout works for the majority of types, including structs, which can be helpful for debugging.
The printn
variant appends a newline.
import std::io;
enum Heat
{
WARM,
WARMER,
REALLY_WARM,
}
fn void main()
{
int[<2>] vec = { 4, 2 };
Heat weather = WARM;
int[5] fib = { 0, 1, 1, 2, 3 };
String dialogue = "secret";
io::print("Hello"); // Hello
io::print(20); // 20
io::print(2.2); // 2.200000
io::print(vec); // [<4, 2>]
io::print(weather); // WARM
io::print(fib); // [0, 1, 1, 2, 3]
io::print(dialogue); // secret
}
### void eprint(x), void eprintn(x)
Print any value to stderr.
The `eprintn` variant appends a newline.
See `print` for usage.
### usz! printf(String format, args...) @maydiscard
Regular printf functionality: `%s`, `%x`, `%d`, `%f` and `%p` are supported.
Will also print enums and vectors. Prints to stdout.
```c3
import std::io;
enum Heat
{
WARM,
WARMER,
REALLY_WARM,
}
fn void main()
{
int[<2>] vec = { 4, 2 };
Heat weather = REALLY_WARM;
String dialogue = "Hello";
io::printfn("%s", dialogue); // Hello
io::printfn("%d", 20); // 20
io::printfn("%f", 2.2); // 2.200000
io::printfn("%s", vec); // [<4, 2>]
io::printfn("%s", weather); // REALLY_WARM
}
Also available as `printfn` which appends a newline.
### usz! eprintf(String format, args...) @maydiscard
Regular printf functionality: `%s`, `%x`, `%d`, `%f` and `%p` are supported.
Will also print enums and vectors. Prints to stderr.
Also available as `eprintfn` which appends a newline.
See `printf` for usage
### char[]! bprintf(char[] buffer, String format, args...) @maydiscard
Prints using a 'printf'-style formatting string, to a string buffer.
Returns a slice of the `buffer` argument with the resulting length.
### usz! fprint(out, x), usz! fprintn(out, x = "")
Print a value to a stream. `out` must implement `OutStream`.
The `fprintn` variant appends a newline.
### usz! fprintf(OutStream out, String format, args...)
Prints to the specified OutStream using a 'printf'-style formatting string.
Returns the number of characters printed.
`fprintfn` appends a newline.
### void putchar(char c) @inline
Libc `putchar`, prints a single character to stdout.
### usz! DString.appendf(DString* str, String format, args...) @maydiscard
Same as printf but on dynamic strings.
### File* stdout(), File* stdin(), File* stderr()
Return stdout, stdin and stderr respectively.
## std::io::file
### File! open(String filename, String mode)
Open a file with the given file name with the given mode (r, w etc)
### File! open_path(Path path, String mode)
Open a file pointed to by a Path struct, with the given mode.
### bool is_file(String path)
See whether the given path is a file.
### usz! get_size(String path)
Get the size of a file.
### void! delete(String filename)
Delete a file.
### void! File.reopen(&self, String filename, String mode)
Reopen a file with a new filename and mode.
### usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET)
Seek in a file. Based on the libc function.
### void! File.write_byte(&self, char c) @dynamic
Write a single byte to a file.
### void! File.close(&self) @inline @dynamic
Close a file, based on the libc function.
### bool File.eof(&self) @inline
True if EOF has been reached. Based on the libc function.
### usz! File.read(&self, char[] buffer)
Read into a buffer, based on the libc function.
### usz! File.write(&self, char[] buffer)
Write to a buffer, based on the libc function.
### char! File.read_byte(&self) @dynamic
Read a single byte from a file.
### char[]! load_buffer(String filename, char[] buffer)
Load up to buffer.len characters into the buffer.
Returns IoError.OVERFLOW if the file is longer than the buffer.
### char[]! load_new(String filename, Allocator allocator = allocator::heap())
Load the entire file into a new buffer.
### char[]! load_temp(String filename)
Load the entire file into a buffer allocated using the temporary allocator.
### void! File.flush(&self) @dynamic
Flush a file, based on the libc function.
## std::collections::list(\<Type\>)
Generic list module, elements are of `Type`.
```c
import std::collections::list;
def MyIntList = List(<int>);
...
MyIntList list;
list.push(123);
list.free();
List.push(List *list, Type element), append(…)
Append a single value to the list.
Type List.pop(List* list)
Removes and returns the last entry in the list.
Type List.pop_first(List *list)
Removes the first entry in the list.
void List.remove_at(List *list, usz index)
Removes the entry at index
.
void List.insert_at(List *list, usz index, Type type)
Inserts a value at index
.
void List.push_front(List *list, Type type)
Inserts a value to the front of the list.
void List.remove_last(List* list)
Remove the last value of the list.
void List.remove_first(List *list)
Remove the first element in the list.
Type List.first(List list)
Return the first element in the list if available.
Type List.last(List list)
Return the last element in the list if available.
List.is_empty(List *list)
Return true
if the list is empty.
usz List.len(List *list)
Return the number of elements in the list.
Type List.get(List *list, usz index)
Return the value at index
.
void List.free(List *list)
Free all memory associated with this list.
void List.swap(List *list, usz i, usz j)
Swap two elements in the list.
title: Standard Library Reference description: Standard Library Reference sidebar:
order: 141
libc
distinct Errno = inline CInt;
struct DivResult
struct LongDivResult
struct TimeSpec
struct Timespec
struct Tm
fn TimeSpec Duration.to_timespec(self) @inline
fn TimeSpec NanoDuration.to_timespec(self) @inline
fn Errno errno()
fn void errno_set(Errno e)
libc @if(!env::LIBC)
fn void* calloc(usz count, usz size) @weak @extern("calloc") @nostrip
fn CFile fclose(CFile) @weak @extern("fclose") @nostrip
fn int feof(CFile stream) @weak @extern("feof") @nostrip
fn int fflush(CFile stream) @weak @extern("fflush") @nostrip
fn int fgetc(CFile stream) @weak @extern("fgetc") @nostrip
fn char* fgets(ZString str, int n, CFile stream) @weak @extern("fgets") @nostrip
fn CFile fopen(ZString filename, ZString mode) @weak @extern("fopen") @nostrip
fn int fputc(int c, CFile stream) @weak @extern("fputc") @nostrip
fn usz fread(void* ptr, usz size, usz nmemb, CFile stream) @weak @extern("fread") @nostrip
fn void* free(void*) @weak @extern("free")
fn CFile freopen(ZString filename, ZString mode, CFile stream) @weak @extern("fopen") @nostrip
fn int fseek(CFile stream, SeekIndex offset, int whence) @weak @extern("fseek") @nostrip
fn usz fwrite(void* ptr, usz size, usz nmemb, CFile stream) @weak @extern("fwrite") @nostrip
fn void longjmp(JmpBuf* buffer, CInt value) @weak @extern("longjmp") @nostrip
fn void* malloc(usz size) @weak @extern("malloc") @nostrip
fn void* memcpy(void* dest, void* src, usz n) @weak @extern("memcpy") @nostrip
fn void* memmove(void* dest, void* src, usz n) @weak @extern("memmove") @nostrip
fn void* memset(void* dest, CInt value, usz n) @weak @extern("memset") @nostrip
fn int putc(int c, CFile stream) @weak @extern("putc") @nostrip
fn int putchar(int c) @weak @extern("putchar") @nostrip
fn int puts(ZString str) @weak @extern("puts") @nostrip
fn void* realloc(void* ptr, usz size) @weak @extern("realloc") @nostrip
fn CInt setjmp(JmpBuf* buffer) @weak @extern("setjmp") @nostrip
libc @if(env::DARWIN)
struct Stat
macro CFile stderr()
macro CFile stdin()
macro CFile stdout()
libc @if(env::LIBC && !env::WIN32 && !env::LINUX && !env::DARWIN)
macro CFile stderr() { return (CFile*)(uptr)STDERR_FD; }
macro CFile stdin() { return (CFile*)(uptr)STDIN_FD; }
macro CFile stdout() { return (CFile*)(uptr)STDOUT_FD; }
libc @if(env::LINUX)
struct Stat @if(!env::X86_64)
struct Stat @if(env::X86_64)
macro usz malloc_size(void* ptr)
macro CFile stderr()
macro CFile stdin()
macro CFile stdout()
libc @if(env::POSIX)
struct Sigaction
struct Stack_t
libc @if(env::WIN32)
struct SystemInfo
macro Tm* gmtime_r(Time_t* timer, Tm* buf)
macro Tm* localtime_r(Time_t* timer, Tm* buf)
macro usz malloc_size(void* ptr)
macro isz read(Fd fd, void* buffer, usz buffer_size)
macro CInt setjmp(JmpBuf* buffer)
macro CFile stderr()
macro CFile stdin()
macro CFile stdout()
macro isz write(Fd fd, void* buffer, usz count)
libc::os
fn int errno() @if(ERRNO_DEFAULT)
macro int errno() @if(env::DARWIN)
macro int errno() @if(env::LINUX)
macro int errno() @if(env::WIN32)
fn void errno_set(int err) @if(ERRNO_DEFAULT)
macro void errno_set(int err) @if(env::DARWIN)
macro void errno_set(int err) @if(env::LINUX)
macro void errno_set(int err) @if(env::WIN32)
std::ascii
fn char char.from_hex(char c)
fn bool char.in_range(char c, char start, char len)
fn bool char.is_alnum(char c)
fn bool char.is_alpha(char c)
fn bool char.is_bdigit(char c)
fn bool char.is_blank(char c)
fn bool char.is_cntrl(char c)
fn bool char.is_digit(char c)
fn bool char.is_graph(char c)
fn bool char.is_lower(char c)
fn bool char.is_odigit(char c)
fn bool char.is_print(char c)
fn bool char.is_punct(char c)
fn bool char.is_space(char c)
fn bool char.is_upper(char c)
fn bool char.is_xdigit(char c)
fn char char.to_lower(char c)
fn char char.to_upper(char c)
fn bool in_range(char c, char start, char len)
macro bool in_range_m(c, start, len)
fn bool is_alnum(char c)
macro bool is_alnum_m(c)
fn bool is_alpha(char c)
macro bool is_alpha_m(c)
fn bool is_bdigit(char c)
macro bool is_bdigit_m(c)
fn bool is_blank(char c)
macro bool is_blank_m(c)
fn bool is_cntrl(char c)
macro bool is_cntrl_m(c)
fn bool is_digit(char c)
macro bool is_digit_m(c)
fn bool is_graph(char c)
macro bool is_graph_m(c)
fn bool is_lower(char c)
macro bool is_lower_m(c)
fn bool is_odigit(char c)
macro bool is_odigit_m(c)
fn bool is_print(char c)
macro bool is_print_m(c)
fn bool is_punct(char c)
macro bool is_punct_m(c)
fn bool is_space(char c)
macro bool is_space_m(c)
fn bool is_upper(char c)
macro bool is_upper_m(c)
fn bool is_xdigit(char c)
macro bool is_xdigit_m(c)
fn char to_lower(char c)
macro to_lower_m(c)
fn char to_upper(char c)
macro to_upper_m(c)
fn bool uint.in_range(uint c, uint start, uint len)
fn bool uint.is_alnum(uint c)
fn bool uint.is_alpha(uint c)
fn bool uint.is_bdigit(uint c)
fn bool uint.is_blank(uint c)
fn bool uint.is_cntrl(uint c)
fn bool uint.is_digit(uint c)
fn bool uint.is_graph(uint c)
fn bool uint.is_lower(uint c)
fn bool uint.is_odigit(uint c)
fn bool uint.is_print(uint c)
fn bool uint.is_punct(uint c)
fn bool uint.is_space(uint c)
fn bool uint.is_upper(uint c)
fn bool uint.is_xdigit(uint c)
fn uint uint.to_lower(uint c)
fn uint uint.to_upper(uint c)
std::atomic
macro @__atomic_compare_exchange_ordering_failure(ptr, expected, desired, $success, failure, $alignment)
macro @__atomic_compare_exchange_ordering_success(ptr, expected, desired, success, failure, $alignment)
fn CInt __atomic_compare_exchange(CInt size, any ptr, any expected, any desired, CInt success, CInt failure) @extern("__atomic_compare_exchange") @export
macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro fetch_max(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_min(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile = false, usz $alignment = 0)
macro flag_clear(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT)
std::atomic::types(<Type>)
struct Atomic
macro Type Atomic.add(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.and(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.div(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.load(&self, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.max(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.min(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.mul(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.or(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.shift_left(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro Type Atomic.shift_right(&self, uint amount, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
macro void Atomic.store(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
macro Type Atomic.sub(&self, Type value, AtomicOrdering ordering = SEQ_CONSISTENT)
fn Type Atomic.xor(&self, uint value, AtomicOrdering ordering = SEQ_CONSISTENT) @if(!types::is_float(Type))
std::bits
macro bswap(i) @builtin
macro char.clz(self)
macro char.ctz(self)
macro char char.fshl(hi, char lo, char shift)
macro char char.fshr(hi, char lo, char shift)
macro char.popcount(self)
macro char char.rotl(self, char shift)
macro char char.rotr(self, char shift)
macro char[<*>].clz(self)
macro char[<*>].ctz(self)
macro char[<*>] char[<*>].fshl(hi, char[<*>] lo, char[<*>] shift)
macro char[<*>] char[<*>].fshr(hi, char[<*>] lo, char[<*>] shift)
macro char[<*>].popcount(self)
macro char[<*>] char[<*>].rotl(self, char[<*>] shift)
macro char[<*>] char[<*>].rotr(self, char[<*>] shift)
macro ichar.clz(self)
macro ichar.ctz(self)
macro ichar ichar.fshl(hi, ichar lo, ichar shift)
macro ichar ichar.fshr(hi, ichar lo, ichar shift)
macro ichar.popcount(self)
macro ichar ichar.rotl(self, ichar shift)
macro ichar ichar.rotr(self, ichar shift)
macro ichar[<*>].clz(self)
macro ichar[<*>].ctz(self)
macro ichar[<*>] ichar[<*>].fshl(hi, ichar[<*>] lo, ichar[<*>] shift)
macro ichar[<*>] ichar[<*>].fshr(hi, ichar[<*>] lo, ichar[<*>] shift)
macro ichar[<*>].popcount(self)
macro ichar[<*>] ichar[<*>].rotl(self, ichar[<*>] shift)
macro ichar[<*>] ichar[<*>].rotr(self, ichar[<*>] shift)
macro int.clz(self)
macro int.ctz(self)
macro int int.fshl(hi, int lo, int shift)
macro int int.fshr(hi, int lo, int shift)
macro int.popcount(self)
macro int int.rotl(self, int shift)
macro int int.rotr(self, int shift)
macro int128.clz(self)
macro int128.ctz(self)
macro int128 int128.fshl(hi, int128 lo, int128 shift)
macro int128 int128.fshr(hi, int128 lo, int128 shift)
macro int128.popcount(self)
macro int128 int128.rotl(self, int128 shift)
macro int128 int128.rotr(self, int128 shift)
macro int128[<*>].clz(self)
macro int128[<*>].ctz(self)
macro int128[<*>] int128[<*>].fshl(hi, int128[<*>] lo, int128[<*>] shift)
macro int128[<*>] int128[<*>].fshr(hi, int128[<*>] lo, int128[<*>] shift)
macro int128[<*>].popcount(self)
macro int128[<*>] int128[<*>].rotl(self, int128[<*>] shift)
macro int128[<*>] int128[<*>].rotr(self, int128[<*>] shift)
macro int[<*>].clz(self)
macro int[<*>].ctz(self)
macro int[<*>] int[<*>].fshl(hi, int[<*>] lo, int[<*>] shift)
macro int[<*>] int[<*>].fshr(hi, int[<*>] lo, int[<*>] shift)
macro int[<*>].popcount(self)
macro int[<*>] int[<*>].rotl(self, int[<*>] shift)
macro int[<*>] int[<*>].rotr(self, int[<*>] shift)
macro long.clz(self)
macro long.ctz(self)
macro long long.fshl(hi, long lo, long shift)
macro long long.fshr(hi, long lo, long shift)
macro long.popcount(self)
macro long long.rotl(self, long shift)
macro long long.rotr(self, long shift)
macro long[<*>].clz(self)
macro long[<*>].ctz(self)
macro long[<*>] long[<*>].fshl(hi, long[<*>] lo, long[<*>] shift)
macro long[<*>] long[<*>].fshr(hi, long[<*>] lo, long[<*>] shift)
macro long[<*>].popcount(self)
macro long[<*>] long[<*>].rotl(self, long[<*>] shift)
macro long[<*>] long[<*>].rotr(self, long[<*>] shift)
macro reverse(i)
macro short.clz(self)
macro short.ctz(self)
macro short short.fshl(hi, short lo, short shift)
macro short short.fshr(hi, short lo, short shift)
macro short.popcount(self)
macro short short.rotl(self, short shift)
macro short short.rotr(self, short shift)
macro short[<*>].clz(self)
macro short[<*>].ctz(self)
macro short[<*>] short[<*>].fshl(hi, short[<*>] lo, short[<*>] shift)
macro short[<*>] short[<*>].fshr(hi, short[<*>] lo, short[<*>] shift)
macro short[<*>].popcount(self)
macro short[<*>] short[<*>].rotl(self, short[<*>] shift)
macro short[<*>] short[<*>].rotr(self, short[<*>] shift)
macro uint.clz(self)
macro uint.ctz(self)
macro uint uint.fshl(hi, uint lo, uint shift)
macro uint uint.fshr(hi, uint lo, uint shift)
macro uint.popcount(self)
macro uint uint.rotl(self, uint shift)
macro uint uint.rotr(self, uint shift)
macro uint128.clz(self)
macro uint128.ctz(self)
macro uint128 uint128.fshl(hi, uint128 lo, uint128 shift)
macro uint128 uint128.fshr(hi, uint128 lo, uint128 shift)
macro uint128.popcount(self)
macro uint128 uint128.rotl(self, uint128 shift)
macro uint128 uint128.rotr(self, uint128 shift)
macro uint128[<*>].clz(self)
macro uint128[<*>].ctz(self)
macro uint128[<*>] uint128[<*>].fshl(hi, uint128[<*>] lo, uint128[<*>] shift)
macro uint128[<*>] uint128[<*>].fshr(hi, uint128[<*>] lo, uint128[<*>] shift)
macro uint128[<*>].popcount(self)
macro uint128[<*>] uint128[<*>].rotl(self, uint128[<*>] shift)
macro uint128[<*>] uint128[<*>].rotr(self, uint128[<*>] shift)
macro uint[<*>].clz(self)
macro uint[<*>].ctz(self)
macro uint[<*>] uint[<*>].fshl(hi, uint[<*>] lo, uint[<*>] shift)
macro uint[<*>] uint[<*>].fshr(hi, uint[<*>] lo, uint[<*>] shift)
macro uint[<*>].popcount(self)
macro uint[<*>] uint[<*>].rotl(self, uint[<*>] shift)
macro uint[<*>] uint[<*>].rotr(self, uint[<*>] shift)
macro ulong.clz(self)
macro ulong.ctz(self)
macro ulong ulong.fshl(hi, ulong lo, ulong shift)
macro ulong ulong.fshr(hi, ulong lo, ulong shift)
macro ulong.popcount(self)
macro ulong ulong.rotl(self, ulong shift)
macro ulong ulong.rotr(self, ulong shift)
macro ulong[<*>].clz(self)
macro ulong[<*>].ctz(self)
macro ulong[<*>] ulong[<*>].fshl(hi, ulong[<*>] lo, ulong[<*>] shift)
macro ulong[<*>] ulong[<*>].fshr(hi, ulong[<*>] lo, ulong[<*>] shift)
macro ulong[<*>].popcount(self)
macro ulong[<*>] ulong[<*>].rotl(self, ulong[<*>] shift)
macro ulong[<*>] ulong[<*>].rotr(self, ulong[<*>] shift)
macro ushort.clz(self)
macro ushort.ctz(self)
macro ushort ushort.fshl(hi, ushort lo, ushort shift)
macro ushort ushort.fshr(hi, ushort lo, ushort shift)
macro ushort.popcount(self)
macro ushort ushort.rotl(self, ushort shift)
macro ushort ushort.rotr(self, ushort shift)
macro ushort[<*>].clz(self)
macro ushort[<*>].ctz(self)
macro ushort[<*>] ushort[<*>].fshl(hi, ushort[<*>] lo, ushort[<*>] shift)
macro ushort[<*>] ushort[<*>].fshr(hi, ushort[<*>] lo, ushort[<*>] shift)
macro ushort[<*>].popcount(self)
macro ushort[<*>] ushort[<*>].rotl(self, ushort[<*>] shift)
macro ushort[<*>] ushort[<*>].rotr(self, ushort[<*>] shift)
std::collections::anylist
struct AnyList (Printable)
macro any AnyList.@item_at(&self, usz index) @operator([])
fn void AnyList.add_all(&self, AnyList* other_list)
fn any[] AnyList.array_view(&self)
fn void AnyList.clear(&self)
macro AnyList.first(&self, $Type)
fn any! AnyList.first_any(&self) @inline
fn void AnyList.free(&self)
fn void AnyList.free_element(&self, any element) @inline
macro AnyList.get(&self, usz index, $Type)
fn any AnyList.get_any(&self, usz index) @inline
fn bool AnyList.is_empty(&self) @inline
macro AnyList.last(&self, $Type)
fn any! AnyList.last_any(&self) @inline
fn usz AnyList.len(&self) @operator(len) @inline
fn AnyList* AnyList.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
fn any! AnyList.new_pop(&self, Allocator allocator = allocator::heap())
fn any! AnyList.new_pop_first(&self, Allocator allocator = allocator::heap())
macro AnyList.pop(&self, $Type)
macro AnyList.pop_first(&self, $Type)
fn any! AnyList.pop_first_retained(&self)
fn any! AnyList.pop_retained(&self)
macro void AnyList.push(&self, element)
macro void AnyList.push_front(&self, type)
fn void AnyList.remove_at(&self, usz index)
fn void AnyList.remove_first(&self)
fn usz AnyList.remove_if(&self, AnyPredicate filter)
fn void AnyList.remove_last(&self)
fn usz AnyList.remove_using_test(&self, AnyTest filter, any context)
fn void AnyList.reserve(&self, usz min_capacity)
fn usz AnyList.retain_if(&self, AnyPredicate selection)
fn usz AnyList.retain_using_test(&self, AnyTest filter, any context)
fn void AnyList.reverse(&self)
macro void AnyList.set(&self, usz index, value)
fn void AnyList.swap(&self, usz i, usz j)
fn AnyList* AnyList.temp_init(&self, usz initial_capacity = 16)
fn any! AnyList.temp_pop(&self)
fn any! AnyList.temp_pop_first(&self)
fn usz! AnyList.to_format(&self, Formatter* formatter) @dynamic
fn String AnyList.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String AnyList.to_tstring(&self)
std::collections::bitset(<SIZE>)
struct BitSet
fn usz BitSet.cardinality(&self)
fn bool BitSet.get(&self, usz i) @operator([]) @inline
fn usz BitSet.len(&self) @operator(len) @inline
fn void BitSet.set(&self, usz i)
fn void BitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
fn void BitSet.unset(&self, usz i)
std::collections::enummap(<Enum, ValueType>)
struct EnumMap (Printable)
fn ValueType EnumMap.get(&self, Enum key) @operator([]) @inline
fn ValueType* EnumMap.get_ref(&self, Enum key) @operator(&[]) @inline
fn void EnumMap.init(&self, ValueType init_value)
fn usz EnumMap.len(&self) @operator(len) @inline
fn void EnumMap.set(&self, Enum key, ValueType value) @operator([]=) @inline
fn usz! EnumMap.to_format(&self, Formatter* formatter) @dynamic
fn String EnumMap.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String EnumMap.to_tstring(&self) @dynamic
std::collections::enumset(<Enum>)
distinct EnumSet (Printable) = EnumSetType;
fn void EnumSet.add(&self, Enum v)
fn void EnumSet.add_all(&self, EnumSet s)
fn EnumSet EnumSet.and_of(&self, EnumSet s)
fn void EnumSet.clear(&self)
fn EnumSet EnumSet.diff_of(&self, EnumSet s)
fn bool EnumSet.has(&self, Enum v)
fn EnumSet EnumSet.or_of(&self, EnumSet s)
fn bool EnumSet.remove(&self, Enum v)
fn void EnumSet.remove_all(&self, EnumSet s)
fn void EnumSet.retain_all(&self, EnumSet s)
fn usz! EnumSet.to_format(&set, Formatter* formatter) @dynamic
fn String EnumSet.to_new_string(&set, Allocator allocator = allocator::heap()) @dynamic
fn String EnumSet.to_tstring(&set) @dynamic
fn EnumSet EnumSet.xor_of(&self, EnumSet s)
std::collections::enumset::private
macro typeid type_for_enum_elements(usz $elements)
std::collections::growablebitset(<Type>)
struct GrowableBitSet
fn usz GrowableBitSet.cardinality(&self)
fn void GrowableBitSet.free(&self)
fn bool GrowableBitSet.get(&self, usz i) @operator([]) @inline
fn usz GrowableBitSet.len(&self) @operator(len)
fn GrowableBitSet* GrowableBitSet.new_init(&self, usz initial_capacity = 1, Allocator allocator = allocator::heap())
fn void GrowableBitSet.set(&self, usz i)
fn void GrowableBitSet.set_bool(&self, usz i, bool value) @operator([]=) @inline
fn GrowableBitSet* GrowableBitSet.temp_init(&self, usz initial_capacity = 1)
fn void GrowableBitSet.unset(&self, usz i)
std::collections::linkedlist(<Type>)
struct LinkedList
fn void LinkedList.clear(&self)
fn Type! LinkedList.first(&self)
fn void LinkedList.free(&self)
fn Type LinkedList.get(&self, usz index)
fn void LinkedList.insert_at(&self, usz index, Type element)
fn Type! LinkedList.last(&self)
fn usz LinkedList.len(&self) @inline
fn LinkedList* LinkedList.new_init(&self, Allocator allocator = allocator::heap())
macro Node* LinkedList.node_at_index(&self, usz index)
fn Type! LinkedList.peek(&self)
fn Type! LinkedList.peek_last(&self)
fn Type! LinkedList.pop(&self)
fn Type! LinkedList.pop_front(&self)
fn void LinkedList.push(&self, Type value)
fn void LinkedList.push_front(&self, Type value)
fn usz LinkedList.remove(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn void LinkedList.remove_at(&self, usz index)
fn void! LinkedList.remove_first(&self) @maydiscard
fn bool LinkedList.remove_first_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn void! LinkedList.remove_last(&self) @maydiscard
fn bool LinkedList.remove_last_match(&self, Type t) @if(ELEMENT_IS_EQUATABLE)
fn void LinkedList.set(&self, usz index, Type element)
fn LinkedList* LinkedList.temp_init(&self)
std::collections::list(<Type>)
struct List (Printable)
macro Type List.@item_at(&self, usz index) @operator([])
fn void List.add_all(&self, List* other_list)
fn void List.add_array(&self, Type[] array)
fn Type[] List.array_view(&self)
fn usz List.byte_size(&self) @inline
fn void List.clear(&self)
fn usz List.compact(&self) @if(ELEMENT_IS_POINTER)
fn usz List.compact_count(&self) @if(ELEMENT_IS_POINTER)
fn bool List.contains(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn bool List.equals(&self, List other_list) @if(ELEMENT_IS_EQUATABLE)
fn Type! List.first(&self)
fn void List.free(&self)
fn Type List.get(&self, usz index) @inline
fn Type* List.get_ref(&self, usz index) @operator(&[]) @inline
fn usz! List.index_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn void List.init_wrapping_array(&self, Type[] types, Allocator allocator = allocator::heap())
fn void List.insert_at(&self, usz index, Type type)
fn bool List.is_empty(&self) @inline
fn Type! List.last(&self)
fn usz List.len(&self) @operator(len) @inline
fn List* List.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap())
fn Type! List.pop(&self)
fn Type! List.pop_first(&self)
fn void List.push(&self, Type element) @inline
fn void List.push_front(&self, Type type) @inline
fn void List.remove_all_from(&self, List* other_list) @if(ELEMENT_IS_EQUATABLE)
fn usz List.remove_all_matches(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn void List.remove_at(&self, usz index)
fn void! List.remove_first(&self) @maydiscard
fn bool List.remove_first_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn usz List.remove_if(&self, ElementPredicate filter)
fn void! List.remove_last(&self) @maydiscard
fn bool List.remove_last_match(&self, Type value) @if(ELEMENT_IS_EQUATABLE)
fn usz List.remove_using_test(&self, ElementTest filter, any context)
fn void List.reserve(&self, usz min_capacity)
fn usz List.retain_if(&self, ElementPredicate selection)
fn usz List.retain_using_test(&self, ElementTest filter, any context)
fn void List.reverse(&self)
fn usz! List.rindex_of(&self, Type type) @if(ELEMENT_IS_EQUATABLE)
fn void List.set(&self, usz index, Type value) @operator([]=)
fn void List.set_at(&self, usz index, Type type)
fn void List.swap(&self, usz i, usz j)
fn List* List.temp_init(&self, usz initial_capacity = 16)
fn usz! List.to_format(&self, Formatter* formatter) @dynamic
fn Type[] List.to_new_array(&self, Allocator allocator = allocator::heap())
fn String List.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn Type[] List.to_tarray(&self)
fn String List.to_tstring(&self)
std::collections::map(<Key, Value>)
struct Entry
struct HashMap
macro HashMap.@each(map; @body(key, value))
macro HashMap.@each_entry(map; @body(entry))
macro Value HashMap.@get_or_set(&map, Key key, Value #expr)
fn void HashMap.clear(&map)
fn void HashMap.free(&map)
fn Value! HashMap.get(&map, Key key) @operator([])
fn Entry*! HashMap.get_entry(&map, Key key)
fn Value*! HashMap.get_ref(&map, Key key)
fn bool HashMap.has_key(&map, Key key)
fn bool HashMap.has_value(&map, Value v) @if(VALUE_IS_EQUATABLE)
fn bool HashMap.is_empty(&map) @inline
fn bool HashMap.is_initialized(&map)
fn Key[] HashMap.key_new_list(&map, Allocator allocator = allocator::heap())
fn Key[] HashMap.key_tlist(&map)
fn usz HashMap.len(&map) @inline
fn HashMap* HashMap.new_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR, Allocator allocator = allocator::heap())
fn HashMap* HashMap.new_init_from_map(&self, HashMap* other_map, Allocator allocator = allocator::heap())
fn void! HashMap.remove(&map, Key key) @maydiscard
fn bool HashMap.set(&map, Key key, Value value) @operator([]=)
fn HashMap* HashMap.temp_init(&self, uint capacity = DEFAULT_INITIAL_CAPACITY, float load_factor = DEFAULT_LOAD_FACTOR)
fn HashMap* HashMap.temp_init_from_map(&map, HashMap* other_map)
fn Value[] HashMap.value_new_list(&map, Allocator allocator = allocator::heap())
fn Value[] HashMap.value_tlist(&map)
std::collections::maybe(<Type>)
struct Maybe
macro Type! Maybe.get(self)
fn Maybe value(Type val)
std::collections::object
struct Object (Printable)
fn void Object.free(&self)
fn Object*! Object.get(&self, String key)
fn Object* Object.get_at(&self, usz index)
fn bool! Object.get_bool(&self, String key)
fn bool! Object.get_bool_at(&self, usz index)
fn char! Object.get_char(&self, String key)
fn char! Object.get_char_at(&self, usz index)
macro String! Object.get_enum(&self, $EnumType, String key)
macro String! Object.get_enum_at(&self, $EnumType, usz index)
fn double! Object.get_float(&self, String key)
fn double! Object.get_float_at(&self, usz index)
fn ichar! Object.get_ichar(&self, String key)
fn ichar! Object.get_ichar_at(&self, usz index)
fn int! Object.get_int(&self, String key)
fn int128! Object.get_int128(&self, String key)
fn int128! Object.get_int128_at(&self, usz index)
fn int! Object.get_int_at(&self, usz index)
fn usz Object.get_len(&self)
fn long! Object.get_long(&self, String key)
fn long! Object.get_long_at(&self, usz index)
fn Object* Object.get_or_create_obj(&self, String key)
fn short! Object.get_short(&self, String key)
fn short! Object.get_short_at(&self, usz index)
fn String! Object.get_string(&self, String key)
fn String! Object.get_string_at(&self, usz index)
fn uint! Object.get_uint(&self, String key)
fn uint128! Object.get_uint128(&self, String key)
fn uint128! Object.get_uint128_at(&self, usz index)
fn uint! Object.get_uint_at(&self, usz index)
fn ulong! Object.get_ulong(&self, String key)
fn ulong! Object.get_ulong_at(&self, usz index)
fn short! Object.get_ushort(&self, String key)
fn ushort! Object.get_ushort_at(&self, usz index)
fn bool Object.has_key(&self, String key)
fn bool Object.is_array(&self) @inline
fn bool Object.is_bool(&self) @inline
fn bool Object.is_empty(&self) @inline
fn bool Object.is_float(&self) @inline
fn bool Object.is_indexable(&self)
fn bool Object.is_int(&self) @inline
fn bool Object.is_keyable(&self)
fn bool Object.is_map(&self) @inline
fn bool Object.is_null(&self) @inline
fn bool Object.is_string(&self) @inline
macro Object* Object.push(&self, value)
fn void Object.push_object(&self, Object* to_append)
macro Object* Object.set(&self, String key, value)
macro Object* Object.set_at(&self, usz index, String key, value)
fn void Object.set_object_at(&self, usz index, Object* to_set)
fn usz! Object.to_format(&self, Formatter* formatter) @dynamic
macro get_integer_value(Object* value, $Type)
fn Object* new_bool(bool b)
macro Object* new_enum(e, Allocator allocator)
fn Object* new_float(double f, Allocator allocator)
fn Object* new_int(int128 i, Allocator allocator)
fn Object* new_null()
fn Object* new_obj(Allocator allocator)
fn Object* new_string(String s, Allocator allocator)
std::collections::priorityqueue(<Type>)
distinct PriorityQueue = inline PrivatePriorityQueue(<Type, false>);
distinct PriorityQueueMax = inline PrivatePriorityQueue(<Type, true>);
std::collections::priorityqueue::private(<Type, MAX>)
struct PrivatePriorityQueue (Printable)
fn Type! PrivatePriorityQueue.first(&self)
fn void PrivatePriorityQueue.free(&self)
fn Type PrivatePriorityQueue.get(&self, usz index) @operator([])
fn bool PrivatePriorityQueue.is_empty(&self)
fn usz PrivatePriorityQueue.len(&self) @operator(len)
fn void PrivatePriorityQueue.new_init(&self, usz initial_capacity = 16, Allocator allocator = allocator::heap()) @inline
fn Type! PrivatePriorityQueue.pop(&self)
fn void PrivatePriorityQueue.push(&self, Type element)
fn void PrivatePriorityQueue.temp_init(&self, usz initial_capacity = 16) @inline
fn usz! PrivatePriorityQueue.to_format(&self, Formatter* formatter) @dynamic
fn String PrivatePriorityQueue.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
std::collections::range(<Type>)
struct ExclusiveRange (Printable)
struct Range (Printable)
fn bool ExclusiveRange.contains(&self, Type value) @inline
fn Type ExclusiveRange.get(&self, usz index) @operator([])
fn usz ExclusiveRange.len(&self) @operator(len)
fn usz! ExclusiveRange.to_format(&self, Formatter* formatter) @dynamic
fn String ExclusiveRange.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String ExclusiveRange.to_tstring(&self)
fn bool Range.contains(&self, Type value) @inline
fn Type Range.get(&self, usz index) @operator([])
fn usz Range.len(&self) @operator(len)
fn usz! Range.to_format(&self, Formatter* formatter) @dynamic
fn String Range.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String Range.to_tstring(&self)
std::collections::ringbuffer(<Type, SIZE>)
struct RingBuffer
fn Type RingBuffer.get(&self, usz index) @operator([])
fn void RingBuffer.init(&self) @inline
fn Type! RingBuffer.pop(&self)
fn void RingBuffer.push(&self, Type c)
fn usz RingBuffer.read(&self, usz index, Type[] buffer)
fn void RingBuffer.write(&self, Type[] buffer)
std::collections::triple(<Type1, Type2, Type3>)
struct Triple
std::collections::tuple(<Type1, Type2>)
struct Tuple
std::core::array
macro concat_new(arr1, arr2, Allocator allocator = allocator::heap())
macro index_of(array, element)
macro rindex_of(array, element)
macro slice2d(array, x = 0, xlen = 0, y = 0, ylen = 0)
macro tconcat(arr1, arr2)
std::core::array::slice(<Type>)
struct Slice2d
macro void Slice2d.@each(&self; @body(usz[<2>], Type))
macro void Slice2d.@each_ref(&self; @body(usz[<2>], Type*))
fn usz Slice2d.count(&self)
macro Type[] Slice2d.get(self, usz idy) @operator([])
fn usz Slice2d.len(&self) @operator(len)
fn Slice2d Slice2d.slice(&self, isz x = 0, isz xlen = 0, isz y = 0, isz ylen = 0)
std::core::bitorder
macro bool is_array_or_slice_of_char(bytes)
macro bool is_arrayptr_or_slice_of_char(bytes)
macro is_bitorder($Type)
macro read(bytes, $Type)
macro write(x, bytes, $Type)
std::core::builtin
enum PrefetchLocality
fault CastResult
fault IteratorResult
fault SearchResult
macro char[] @as_char_view(&value) @builtin
macro anyfault @catch(#expr) @builtin
macro @expect(#value, expected, $probability = 1.0) @builtin
macro bool @likely(bool #value, $probability = 1.0) @builtin
macro bool @ok(#expr) @builtin
macro @prefetch(void* ptr, PrefetchLocality $locality = VERY_NEAR, bool $write = false) @builtin
macro void @scope(&variable; @body) @builtin
macro void @swap(&a, &b) @builtin
macro bool @unlikely(bool #value, $probability = 1.0) @builtin
macro uint String.hash(String c)
macro any.as_inner(&self)
macro any.retype_to(&self, typeid type)
macro any_make(void* ptr, typeid type) @builtin
macro anycast(any v, $Type) @builtin
macro bitcast(expr, $Type) @builtin
macro uint bool.hash(bool b)
macro uint char.hash(char c)
macro uint char[].hash(char[] c)
macro int compare_to(a, b) @builtin
fn void default_panic(String message, String file, String function, uint line) @if(!env::NATIVE_STACKTRACE)
fn void default_panic(String message, String file, String function, uint line) @if(env::NATIVE_STACKTRACE)
macro enum_by_name($Type, String enum_name) @builtin
macro bool equals(a, b) @builtin
macro void* get_frameaddress(int n)
macro void* get_returnaddress(int n)
macro greater(a, b) @builtin
macro greater_eq(a, b) @builtin
macro uint ichar.hash(ichar c)
macro uint int.hash(int i)
macro uint int128.hash(int128 i)
macro less(a, b) @builtin
macro less_eq(a, b) @builtin
macro uint long.hash(long i)
macro max(x, ...) @builtin
macro min(x, ...) @builtin
fn void panicf(String fmt, String file, String function, uint line, args...)
fn bool print_backtrace(String message, int backtraces_to_ignore) @if(env::NATIVE_STACKTRACE)
macro uint short.hash(short s)
macro swizzle(v, ...) @builtin
macro swizzle2(v, v2, ...) @builtin
macro uint typeid.hash(typeid t)
macro uint uint.hash(uint i)
macro uint uint128.hash(uint128 i)
macro uint ulong.hash(ulong i)
macro void unreachable(String string = "Unreachable statement reached.", ...) @builtin @noreturn
macro void unsupported(String string = "Unsupported function invoked") @builtin @noreturn
macro uint ushort.hash(ushort s)
macro uint void*.hash(void* ptr)
std::core::builtin @if((env::LINUX || env::DARWIN) && env::COMPILER_SAFE_MODE && env::DEBUG_SYMBOLS)
fn void sig_bus_error(CInt i)
fn void sig_panic(String message)
fn void sig_segmentation_fault(CInt i)
std::core::dstring
distinct DString (OutStream) = void*;
macro void DString.append(&self, value)
fn void DString.append_char(&self, char c)
fn void DString.append_char32(&self, Char32 c)
fn void DString.append_chars(&self, String str)
fn void DString.append_repeat(&self, char c, usz times)
fn void DString.append_string(&self, DString str)
fn void DString.append_utf32(&self, Char32[] chars)
fn usz! DString.appendf(&self, String format, args...) @maydiscard
fn usz! DString.appendfn(&self, String format, args...) @maydiscard
fn usz DString.capacity(self)
fn void DString.chop(self, usz new_size)
fn void DString.clear(self)
fn DString DString.copy(self, Allocator allocator = null)
fn String DString.copy_str(self, Allocator allocator = allocator::heap())
fn Char32[] DString.copy_utf32(&self, Allocator allocator = allocator::heap())
fn ZString DString.copy_zstr(self, Allocator allocator = allocator::heap())
fn void DString.delete(&self, usz start, usz len = 1)
fn void DString.delete_range(&self, usz start, usz end)
fn bool DString.equals(self, DString other_string)
fn void DString.free(&self)
fn void DString.insert_at(&self, usz index, String s)
fn usz DString.len(&self) @dynamic
fn bool DString.less(self, DString other_string)
fn DString DString.new_concat(self, DString b, Allocator allocator = allocator::heap())
fn DString DString.new_init(&self, usz capacity = MIN_CAPACITY, Allocator allocator = allocator::heap())
fn usz! DString.read_from_stream(&self, InStream reader)
fn void DString.reserve(&self, usz addition)
fn void DString.set(self, usz index, char c)
fn String DString.str_view(self)
fn DString DString.tcopy(&self)
fn String DString.tcopy_str(self)
fn DString DString.temp_concat(self, DString b)
fn DString DString.temp_init(&self, usz capacity = MIN_CAPACITY)
fn usz! DString.write(&self, char[] buffer) @dynamic
fn void! DString.write_byte(&self, char c) @dynamic
fn ZString DString.zstr_view(&self)
fn DString new(String c = "", Allocator allocator = allocator::heap())
fn DString new_join(String[] s, String joiner, Allocator allocator = allocator::heap())
fn DString new_with_capacity(usz capacity, Allocator allocator = allocator::heap())
fn DString temp_new(String s = "")
fn DString temp_with_capacity(usz capacity)
std::core::env
enum ArchType
enum CompilerOptLevel
enum MemoryEnvironment
enum OsType
macro bool os_is_darwin()
macro bool os_is_posix()
std::core::mem
enum AtomicOrdering : int
struct TempState
macro @atomic_load(&x, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
macro void @atomic_store(&x, value, AtomicOrdering $ordering = SEQ_CONSISTENT, $volatile = false) @builtin
macro @clone(value) @builtin @nodiscard
macro @gather_aligned(ptrvec, bool[<*>] mask, passthru, usz $alignment)
macro @masked_load_aligned(ptr, bool[<*>] mask, passthru, usz $alignment)
macro @masked_store_aligned(ptr, value, bool[<*>] mask, usz $alignment)
macro void @pool(TempAllocator* #other_temp = null; @body) @builtin
macro void @report_heap_allocs_in_scope(;@body())
macro @scatter_aligned(ptrvec, value, bool[<*>] mask, usz $alignment)
macro void @scoped(Allocator allocator; @body())
macro void @stack_mem(usz $size; @body(Allocator mem)) @builtin
macro void @stack_pool(usz $size; @body) @builtin
macro @tclone(value) @builtin @nodiscard
macro @volatile_load(&x) @builtin
macro @volatile_store(&x, y) @builtin
fn usz aligned_offset(usz offset, usz alignment)
macro void* aligned_pointer(void* ptr, usz alignment)
macro alloc($Type) @nodiscard
macro alloc_aligned($Type) @nodiscard
macro alloc_array($Type, usz elements) @nodiscard
macro alloc_array_aligned($Type, usz elements) @nodiscard
fn void* calloc(usz size) @builtin @inline @nodiscard
fn void* calloc_aligned(usz size, usz alignment) @builtin @inline @nodiscard
macro void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false, bool $inlined = false)
macro void clear_inline(void* dst, usz $len, usz $dst_align = 0, bool $is_volatile = false)
macro compare_exchange(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT, bool $volatile = true, bool $weak = false, usz $alignment = 0)
macro compare_exchange_volatile(ptr, compare, value, AtomicOrdering $success = SEQ_CONSISTENT, AtomicOrdering $failure = SEQ_CONSISTENT)
macro void copy(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false, bool $inlined = false)
macro void copy_inline(void* dst, void* src, usz $len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
macro bool equals(a, b, isz len = -1, usz $align = 0)
fn void free(void* ptr) @builtin @inline
fn void free_aligned(void* ptr) @builtin @inline
macro gather(ptrvec, bool[<*>] mask, passthru)
macro TrackingEnv* get_tracking_env()
fn void* malloc(usz size) @builtin @inline @nodiscard
macro masked_load(ptr, bool[<*>] mask, passthru)
macro masked_store(ptr, value, bool[<*>] mask)
macro void move(void* dst, void* src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)
macro new($Type, ...) @nodiscard
macro new_aligned($Type, ...) @nodiscard
macro new_array($Type, usz elements) @nodiscard
macro new_array_aligned($Type, usz elements) @nodiscard
fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
fn void* realloc(void *ptr, usz new_size) @builtin @inline @nodiscard
fn void* realloc_aligned(void *ptr, usz new_size, usz alignment) @builtin @inline @nodiscard
macro scatter(ptrvec, value, bool[<*>] mask)
macro void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false)
macro void set_inline(void* dst, char val, usz $len, usz $dst_align = 0, bool $is_volatile = false)
fn void* tcalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
macro temp_alloc($Type) @nodiscard
macro temp_alloc_array($Type, usz elements) @nodiscard
macro temp_new($Type, ...) @nodiscard
macro temp_new_array($Type, usz elements) @nodiscard
fn void temp_pop(TempState old_state)
fn TempState temp_push(TempAllocator* other = null)
fn void* tmalloc(usz size, usz alignment = 0) @builtin @inline @nodiscard
fn void* trealloc(void* ptr, usz size, usz alignment = mem::DEFAULT_MEM_ALIGNMENT) @builtin @inline @nodiscard
macro type_alloc_must_be_aligned($Type)
std::core::mem::allocator
distinct LibcAllocator (Allocator) = uptr;
enum AllocInitType
fault AllocationFailure
interface Allocator
struct AlignedBlock
struct Allocation
struct ArenaAllocator (Allocator)
struct DynamicArenaAllocator (Allocator)
struct OnStackAllocator (Allocator)
struct OnStackAllocatorHeader
struct SimpleHeapAllocator (Allocator)
struct TempAllocator (Allocator)
struct TempAllocatorPage
struct TrackingAllocator (Allocator)
struct TrackingEnv
struct WasmMemory
macro void*! @aligned_alloc(#alloc_fn, usz bytes, usz alignment)
macro void! @aligned_free(#free_fn, void* old_pointer)
macro void*! @aligned_realloc(#calloc_fn, #free_fn, void* old_pointer, usz bytes, usz alignment)
fn void*! ArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void ArenaAllocator.clear(&self)
fn void ArenaAllocator.init(&self, char[] data)
fn usz ArenaAllocator.mark(&self) @dynamic
fn void ArenaAllocator.release(&self, void* ptr, bool) @dynamic
fn void ArenaAllocator.reset(&self, usz mark) @dynamic
fn void*! ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignment) @dynamic
fn void*! DynamicArenaAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void DynamicArenaAllocator.free(&self)
fn void DynamicArenaAllocator.init(&self, usz page_size, Allocator allocator)
fn void DynamicArenaAllocator.release(&self, void* ptr, bool) @dynamic
fn void DynamicArenaAllocator.reset(&self, usz mark = 0) @dynamic
fn void*! DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*! OnStackAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void OnStackAllocator.free(&self)
fn void OnStackAllocator.init(&self, char[] data, Allocator allocator)
fn void OnStackAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
fn void*! OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*! SimpleHeapAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn void SimpleHeapAllocator.init(&self, MemoryAllocFn allocator)
fn void SimpleHeapAllocator.release(&self, void* old_pointer, bool aligned) @dynamic
fn void*! SimpleHeapAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn void*! TempAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn usz TempAllocator.mark(&self) @dynamic
fn void! TempAllocator.print_pages(&self, File* f)
fn void TempAllocator.release(&self, void* old_pointer, bool) @dynamic
fn void TempAllocator.reset(&self, usz mark) @dynamic
fn void*! TempAllocator.resize(&self, void* pointer, usz size, usz alignment) @dynamic
macro bool TempAllocatorPage.is_aligned(&self)
macro usz TempAllocatorPage.pagesize(&self)
fn void*! TrackingAllocator.acquire(&self, usz size, AllocInitType init_type, usz alignment) @dynamic
fn usz TrackingAllocator.allocated(&self)
fn usz TrackingAllocator.allocation_count(&self)
fn Allocation[] TrackingAllocator.allocations_tlist(&self, Allocator allocator)
fn void TrackingAllocator.clear(&self)
fn void! TrackingAllocator.fprint_report(&self, OutStream out)
fn void TrackingAllocator.free(&self)
fn void TrackingAllocator.init(&self, Allocator allocator)
fn void TrackingAllocator.print_report(&self)
fn void TrackingAllocator.release(&self, void* old_pointer, bool is_aligned) @dynamic
fn void*! TrackingAllocator.resize(&self, void* old_pointer, usz size, usz alignment) @dynamic
fn usz TrackingAllocator.total_allocated(&self)
fn usz TrackingAllocator.total_allocation_count(&self)
fn char[]! WasmMemory.allocate_block(&self, usz bytes)
macro alloc(Allocator allocator, $Type) @nodiscard
macro alloc_array(Allocator allocator, $Type, usz elements) @nodiscard
macro alloc_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
macro alloc_array_try(Allocator allocator, $Type, usz elements) @nodiscard
macro alloc_try(Allocator allocator, $Type) @nodiscard
macro alloc_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
macro void* calloc(Allocator allocator, usz size) @nodiscard
macro void*! calloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
macro void*! calloc_try(Allocator allocator, usz size) @nodiscard
macro clone(Allocator allocator, value) @nodiscard
fn any clone_any(Allocator allocator, any value) @nodiscard
macro void free(Allocator allocator, void* ptr)
macro void free_aligned(Allocator allocator, void* ptr)
macro Allocator heap()
macro void* malloc(Allocator allocator, usz size) @nodiscard
macro void*! malloc_aligned(Allocator allocator, usz size, usz alignment) @nodiscard
macro void*! malloc_try(Allocator allocator, usz size) @nodiscard
macro new(Allocator allocator, $Type, ...) @nodiscard
macro new_array(Allocator allocator, $Type, usz elements) @nodiscard
macro new_array_aligned(Allocator allocator, $Type, usz elements) @nodiscard
macro new_array_try(Allocator allocator, $Type, usz elements) @nodiscard
fn TempAllocator*! new_temp_allocator(usz size, Allocator allocator)
macro new_try(Allocator allocator, $Type, ...) @nodiscard
macro new_with_padding(Allocator allocator, $Type, usz padding) @nodiscard
macro void* realloc(Allocator allocator, void* ptr, usz new_size) @nodiscard
macro void*! realloc_aligned(Allocator allocator, void* ptr, usz new_size, usz alignment) @nodiscard
macro void*! realloc_try(Allocator allocator, void* ptr, usz new_size) @nodiscard
macro TempAllocator* temp()
std::core::mem::allocator @if(!env::WIN32 && !env::POSIX)
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
std::core::mem::allocator @if(env::POSIX)
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
std::core::mem::allocator @if(env::WIN32)
fn void*! LibcAllocator.acquire(&self, usz bytes, AllocInitType init_type, usz alignment) @dynamic
fn void LibcAllocator.release(&self, void* old_ptr, bool aligned) @dynamic
fn void*! LibcAllocator.resize(&self, void* old_ptr, usz new_bytes, usz alignment) @dynamic
std::core::runtime
struct AnyRaw
struct BenchmarkUnit
struct SliceRaw
struct TestContext
struct TestUnit
fn BenchmarkUnit[] benchmark_collection_create(Allocator allocator = allocator::heap())
fn int cmp_test_unit(TestUnit a, TestUnit b)
fn bool default_benchmark_runner()
fn bool default_test_runner()
fn bool run_benchmarks(BenchmarkUnit[] benchmarks)
fn bool run_tests(TestUnit[] tests)
fn void set_benchmark_max_iterations(uint value) @builtin
fn void set_benchmark_warmup_iterations(uint value) @builtin
fn TestUnit[] test_collection_create(Allocator allocator = allocator::heap())
fn void test_panic(String message, String file, String function, uint line)
std::core::runtime @if(WASM_NOLIBC)
fn void wasm_initialize() @extern("_initialize") @wasm
std::core::string
distinct WString = inline Char16*;
distinct ZString = inline char*;
fault NumberConversion
fault UnicodeResult
struct Splitter
fn String! Splitter.next(&self)
fn void Splitter.reset(&self)
fn String String.concat(s1, String s2, Allocator allocator = allocator::heap())
fn bool String.contains(s, String needle)
fn void String.convert_ascii_to_lower(s)
fn void String.convert_ascii_to_upper(s)
fn String String.copy(s, Allocator allocator = allocator::heap())
fn bool String.ends_with(string, String needle)
fn void String.free(&s, Allocator allocator = allocator::heap())
fn usz! String.index_of(s, String needle)
fn usz! String.index_of_char(s, char needle)
fn StringIterator String.iterator(s)
fn String String.new_ascii_to_lower(s, Allocator allocator = allocator::heap())
fn String String.new_ascii_to_upper(s, Allocator allocator = allocator::heap())
fn usz! String.rindex_of(s, String needle)
fn usz! String.rindex_of_char(s, char needle)
fn String[] String.split(s, String needle, usz max = 0, Allocator allocator = allocator::heap())
fn Splitter String.splitter(self, String split)
fn bool String.starts_with(string, String needle)
fn String String.strip(string, String needle)
fn String String.strip_end(string, String needle)
fn String String.tconcat(s1, String s2)
fn String String.tcopy(s)
fn String String.temp_ascii_to_lower(s, Allocator allocator = allocator::heap())
fn String String.temp_ascii_to_upper(s)
fn double! String.to_double(s)
fn float! String.to_float(s)
fn ichar! String.to_ichar(s, int base = 10)
fn int! String.to_int(s, int base = 10)
fn int128! String.to_int128(s, int base = 10)
macro String.to_integer(string, $Type, int base = 10)
fn long! String.to_long(s, int base = 10)
fn Char16[]! String.to_new_utf16(s, Allocator allocator = allocator::heap())
fn Char32[]! String.to_new_utf32(s, Allocator allocator = allocator::heap())
fn WString! String.to_new_wstring(s, Allocator allocator = allocator::heap())
fn short! String.to_short(s, int base = 10)
fn Char16[]! String.to_temp_utf16(s)
fn Char32[]! String.to_temp_utf32(s)
fn WString! String.to_temp_wstring(s)
fn char! String.to_uchar(s, int base = 10)
fn uint! String.to_uint(s, int base = 10)
fn uint128! String.to_uint128(s, int base = 10)
fn ulong! String.to_ulong(s, int base = 10)
fn ushort! String.to_ushort(s, int base = 10)
fn String String.trim(string, String to_trim = "\t\n\r ")
fn String[] String.tsplit(s, String needle, usz max = 0)
fn usz String.utf8_codepoints(s)
fn ZString String.zstr_copy(s, Allocator allocator = allocator::heap())
fn ZString String.zstr_tcopy(s)
fn usz ZString.char_len(str)
fn String ZString.copy(z, Allocator allocator = allocator::temp())
fn usz ZString.len(str)
fn String ZString.str_view(str)
fn String ZString.tcopy(z)
macro bool char_in_set(char c, String set)
macro double! decfloat(char[] chars, int $bits, int $emin, int sign)
macro double! hexfloat(char[] chars, int $bits, int $emin, int sign)
fn String join_new(String[] s, String joiner, Allocator allocator = allocator::heap())
macro String new_format(String fmt, ..., Allocator allocator = allocator::heap())
fn String! new_from_utf16(Char16[] utf16, Allocator allocator = allocator::heap())
fn String! new_from_utf32(Char32[] utf32, Allocator allocator = allocator::heap())
fn String! new_from_wstring(WString wstring, Allocator allocator = allocator::heap())
fn String! temp_from_utf16(Char16[] utf16)
fn String! temp_from_wstring(WString wstring)
macro String tformat(String fmt, ...)
std::core::string::conv
fn void! char16_to_utf8_unsafe(Char16 *ptr, usz *available, char** output)
fn void char32_to_utf16_unsafe(Char32 c, Char16** output)
fn usz! char32_to_utf8(Char32 c, char[] output)
fn usz char32_to_utf8_unsafe(Char32 c, char** output)
fn usz utf16len_for_utf32(Char32[] utf32)
fn usz utf16len_for_utf8(String utf8)
fn void! utf16to8_unsafe(Char16[] utf16, char* utf8_buffer)
fn usz! utf32to8(Char32[] utf32, char[] utf8_buffer)
fn void utf32to8_unsafe(Char32[] utf32, char* utf8_buffer)
fn usz utf8_codepoints(String utf8)
fn Char32! utf8_to_char32(char* ptr, usz* size)
fn usz utf8len_for_utf16(Char16[] utf16)
fn usz utf8len_for_utf32(Char32[] utf32)
fn void! utf8to16_unsafe(String utf8, Char16* utf16_buffer)
fn usz! utf8to32(String utf8, Char32[] utf32_buffer)
fn void! utf8to32_unsafe(String utf8, Char32* utf32_buffer)
std::core::string::iterator
struct StringIterator
fn Char32! StringIterator.next(&self)
fn void StringIterator.reset(&self)
std::core::types
enum TypeKind : char
fault ConversionResult
struct TypeEnum
macro bool @has_same(#a, #b, ...)
fn bool TypeKind.is_int(kind) @inline
macro any_to_int(any v, $Type)
macro bool implements_copy($Type)
macro TypeKind inner_kind($Type)
macro bool is_bool($Type)
macro bool is_comparable_value(value)
macro bool is_equatable_type($Type)
macro bool is_equatable_value(value)
macro bool is_float($Type)
macro bool is_floatlike($Type)
macro bool is_int($Type)
macro bool is_intlike($Type)
macro bool is_numerical($Type)
macro bool is_promotable_to_float($Type)
macro bool is_promotable_to_floatlike($Type)
macro bool is_same($TypeA, $TypeB)
macro bool is_same_vector_type($Type1, $Type2)
macro bool is_slice_convertable($Type)
macro bool is_subtype_of($Type, $OtherType)
macro bool is_underlying_int($Type)
macro bool is_vector($Type)
macro lower_to_atomic_compatible_type($Type)
macro bool may_load_atomic($Type)
fn bool typeid.is_subtype_of(self, typeid other)
std::core::values
macro bool @assign_to(#value1, #value2)
macro TypeKind @inner_kind(#value)
macro bool @is_bool(#value)
macro bool @is_float(#value)
macro bool @is_floatlike(#value)
macro bool @is_int(#value)
macro bool @is_promotable_to_float(#value)
macro bool @is_promotable_to_floatlike(#value)
macro bool @is_same_type(#value1, #value2)
macro bool @is_same_vector_type(#value1, #value2)
macro bool @is_vector(#value)
macro typeid @typeid(#value) @builtin
macro bool @typeis(#value, $Type) @builtin
macro TypeKind @typekind(#value) @builtin
macro promote_int(x)
std::crypto::rc4
struct Rc4
fn void Rc4.crypt(&self, char[] in, char[] out)
fn void Rc4.destroy(&self)
fn void Rc4.init(&self, char[] key)
std::encoding::base64
fault Base64Error
struct Base64Decoder
struct Base64Encoder
fn usz! Base64Decoder.decode(&self, char[] src, char[] dst)
fn usz! Base64Decoder.decode_len(&self, usz n)
fn void! Base64Decoder.init(&self, String alphabet, int padding = '=')
fn usz! Base64Encoder.encode(&self, char[] src, char[] dst)
fn usz Base64Encoder.encode_len(&self, usz n)
fn void! Base64Encoder.init(&self, String alphabet, int padding = '=')
std::encoding::csv
struct CsvReader
macro CsvReader.@each_row(self, int rows = int.max; @body(String[] row))
fn void CsvReader.init(&self, InStream stream, String separator = ",")
fn String[]! CsvReader.read_new_row(self, Allocator allocator = allocator::heap())
fn String[]! CsvReader.read_new_row_with_allocator(self, Allocator allocator = allocator::heap())
fn String[]! CsvReader.read_temp_row(self)
fn void! CsvReader.skip_row(self) @maydiscard
std::encoding::json
fault JsonParsingError
fn JsonTokenType! lex_string(JsonContext* context)
fn Object*! parse(InStream s, Allocator allocator = allocator::heap())
std::hash::adler32
struct Adler32
fn uint Adler32.final(&self)
fn void Adler32.init(&self)
fn void Adler32.update(&self, char[] data)
fn void Adler32.updatec(&self, char c)
fn uint encode(char[] data)
std::hash::crc32
struct Crc32
fn uint Crc32.final(&self)
fn void Crc32.init(&self, uint seed = 0)
fn void Crc32.update(&self, char[] data)
fn void Crc32.updatec(&self, char c)
fn uint encode(char[] data)
std::hash::crc64
struct Crc64
fn ulong Crc64.final(&self)
fn void Crc64.init(&self, uint seed = 0)
fn void Crc64.update(&self, char[] data)
fn void Crc64.updatec(&self, char c)
fn ulong encode(char[] data)
std::hash::fnv32a
distinct Fnv32a = uint;
fn void Fnv32a.init(&self)
fn void Fnv32a.update(&self, char[] data)
macro void Fnv32a.update_char(&self, char c)
fn uint encode(char[] data)
std::hash::fnv64a
distinct Fnv64a = ulong;
fn void Fnv64a.init(&self)
fn void Fnv64a.update(&self, char[] data)
macro void Fnv64a.update_char(&self, char c)
fn ulong encode(char[] data)
std::hash::sha1
struct Sha1
fn char[20] Sha1.final(&self)
fn void Sha1.init(&self)
fn void Sha1.update(&self, char[] data)
std::io
enum Seek
fault FormattingFault
fault IoError
fault PrintFault
interface InStream
interface OutStream
interface Printable
struct BitReader
struct BitWriter
struct ByteBuffer (InStream, OutStream)
struct ByteReader (InStream)
struct ByteWriter (OutStream)
struct File (InStream, OutStream)
struct Formatter
struct LimitReader (InStream)
struct ReadBuffer (InStream)
struct Scanner (InStream)
struct WriteBuffer (OutStream)
macro bool @is_instream(#expr)
macro bool @is_outstream(#expr)
macro void! @pushback_using_seek(&s)
macro char! @read_byte_using_read(&s)
macro usz! @read_using_read_byte(&s, char[] buffer)
macro void! @write_byte_using_write(&s, char c)
macro usz! @write_using_write_byte(&s, char[] bytes)
fn void BitReader.clear(&self) @inline
fn void BitReader.init(&self, InStream byte_reader)
fn char! BitReader.read_bits(&self, uint nbits)
fn void! BitWriter.flush(&self)
fn void BitWriter.init(&self, OutStream byte_writer)
fn void! BitWriter.write_bits(&self, uint bits, uint nbits)
fn usz! ByteBuffer.available(&self) @inline @dynamic
fn void ByteBuffer.free(&self)
fn void! ByteBuffer.grow(&self, usz n)
fn ByteBuffer*! ByteBuffer.init_with_buffer(&self, char[] buf)
fn ByteBuffer*! ByteBuffer.new_init(&self, usz max_read, usz initial_capacity = 16, Allocator allocator = allocator::heap())
fn void! ByteBuffer.pushback_byte(&self) @dynamic
fn usz! ByteBuffer.read(&self, char[] bytes) @dynamic
fn char! ByteBuffer.read_byte(&self) @dynamic
fn usz! ByteBuffer.seek(&self, isz offset, Seek seek) @dynamic
macro ByteBuffer.shrink(&self)
fn ByteBuffer*! ByteBuffer.temp_init(&self, usz max_read, usz initial_capacity = 16)
fn usz! ByteBuffer.write(&self, char[] bytes) @dynamic
fn void! ByteBuffer.write_byte(&self, char c) @dynamic
fn usz! ByteReader.available(&self) @inline @dynamic
fn ByteReader* ByteReader.init(&self, char[] bytes)
fn usz ByteReader.len(&self) @dynamic
fn void! ByteReader.pushback_byte(&self) @dynamic
fn usz! ByteReader.read(&self, char[] bytes) @dynamic
fn char! ByteReader.read_byte(&self) @dynamic
fn usz! ByteReader.seek(&self, isz offset, Seek seek) @dynamic
fn usz! ByteReader.write_to(&self, OutStream writer) @dynamic
fn void! ByteWriter.destroy(&self) @dynamic
fn void! ByteWriter.ensure_capacity(&self, usz len) @inline
fn ByteWriter* ByteWriter.init_with_buffer(&self, char[] data)
fn ByteWriter* ByteWriter.new_init(&self, Allocator allocator = allocator::heap())
fn usz! ByteWriter.read_from(&self, InStream reader) @dynamic
fn String ByteWriter.str_view(&self) @inline
fn ByteWriter* ByteWriter.temp_init(&self)
fn usz! ByteWriter.write(&self, char[] bytes) @dynamic
fn void! ByteWriter.write_byte(&self, char c) @dynamic
fn void Formatter.init(&self, OutputFn out_fn, void* data = null)
fn usz! Formatter.print(&self, String str)
fn usz! Formatter.print_with_function(&self, Printable arg)
fn usz! Formatter.printf(&self, String format, args...)
fn usz! Formatter.vprintf(&self, String format, any[] anys)
fn usz! LimitReader.available(&self) @inline @dynamic
fn void! LimitReader.close(&self) @dynamic
fn LimitReader* LimitReader.init(&self, InStream wrapped_stream, usz limit)
fn usz! LimitReader.read(&self, char[] bytes) @dynamic
fn char! LimitReader.read_byte(&self) @dynamic
fn void! ReadBuffer.close(&self) @dynamic
fn ReadBuffer* ReadBuffer.init(&self, InStream wrapped_stream, char[] bytes)
fn usz! ReadBuffer.read(&self, char[] bytes) @dynamic
fn char! ReadBuffer.read_byte(&self) @dynamic
fn String ReadBuffer.str_view(&self) @inline
fn void! Scanner.close(&self) @dynamic
fn char[] Scanner.flush(&self) @dynamic
fn void Scanner.init(&self, InStream stream, char[] buffer)
fn usz! Scanner.read(&self, char[] bytes) @dynamic
fn char! Scanner.read_byte(&self) @dynamic
fn char[]! Scanner.scan(&self, String pattern = "\n")
fn void! WriteBuffer.close(&self) @dynamic
fn void! WriteBuffer.flush(&self) @dynamic
fn WriteBuffer* WriteBuffer.init(&self, OutStream wrapped_stream, char[] bytes)
fn String WriteBuffer.str_view(&self) @inline
fn usz! WriteBuffer.write(&self, char[] bytes) @dynamic
fn void! WriteBuffer.write_byte(&self, char c) @dynamic
fn usz! available(InStream s)
fn char[]! bprintf(char[] buffer, String format, args...) @maydiscard
fn usz! copy_to(InStream in, OutStream dst, char[] buffer = {})
macro void eprint(x)
fn usz! eprintf(String format, args...) @maydiscard
fn usz! eprintfn(String format, args...) @maydiscard
macro void eprintn(x)
macro usz! fprint(out, x)
fn usz! fprintf(OutStream out, String format, args...)
fn usz! fprintfn(OutStream out, String format, args...) @maydiscard
macro usz! fprintn(out, x = "")
macro void print(x)
fn usz! printf(String format, args...) @maydiscard
fn usz! printfn(String format, args...) @maydiscard
macro void printn(x = "")
macro usz! read_all(stream, char[] buffer)
macro usz! read_any(stream, any ref)
macro usz! read_varint(stream, x_ptr)
macro String! readline(stream = io::stdin(), Allocator allocator = allocator::heap())
macro String! treadline(stream = io::stdin())
macro usz! write_all(stream, char[] buffer)
macro usz! write_any(stream, any ref)
macro usz! write_varint(stream, x)
std::io @if (env::LIBC)
fn void putchar(char c) @inline
fn File* stderr()
fn File* stdin()
fn File* stdout()
std::io @if(!env::LIBC)
fn void putchar(char c) @inline
fn File* stderr()
fn File* stdin()
fn File* stdout()
std::io::file
fn void! File.close(&self) @inline @dynamic
fn bool File.eof(&self) @inline
fn void! File.flush(&self) @dynamic
fn void! File.memopen(File* file, char[] data, String mode)
fn usz! File.read(&self, char[] buffer) @dynamic
fn char! File.read_byte(&self) @dynamic
fn void! File.reopen(&self, String filename, String mode)
fn usz! File.seek(&self, isz offset, Seek seek_mode = Seek.SET) @dynamic
fn usz! File.write(&self, char[] buffer) @dynamic
fn void! File.write_byte(&self, char c) @dynamic
fn void! delete(String filename)
fn File from_handle(CFile file)
fn usz! get_size(String path)
fn bool is_file(String path)
fn char[]! load_buffer(String filename, char[] buffer)
fn char[]! load_new(String filename, Allocator allocator = allocator::heap())
fn char[]! load_temp(String filename)
fn File! open(String filename, String mode)
fn File! open_path(Path path, String mode)
std::io::os
macro String! getcwd(Allocator allocator = allocator::heap())
macro void! native_chdir(Path path)
fn bool native_file_or_dir_exists(String path)
fn usz! native_file_size(String path) @if(!env::WIN32 && !env::DARWIN)
fn usz! native_file_size(String path) @if(env::DARWIN)
fn usz! native_file_size(String path) @if(env::WIN32)
fn bool native_is_dir(String path)
fn bool native_is_file(String path)
macro bool! native_mkdir(Path path, MkdirPermissions permissions)
macro bool! native_rmdir(Path path)
fn void! native_stat(Stat* stat, String path) @if(env::DARWIN || env::LINUX)
std::io::os @if(env::LIBC)
fn void*! native_fopen(String filename, String mode) @inline
fn usz! native_fread(CFile file, char[] buffer) @inline
fn void*! native_freopen(void* file, String filename, String mode) @inline
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
fn usz! native_ftell(CFile file) @inline
fn usz! native_fwrite(CFile file, char[] buffer) @inline
fn void! native_remove(String filename)
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(!env::WIN32)
fn Path! native_temp_directory(Allocator allocator = allocator::heap()) @if(env::WIN32)
std::io::os @if(env::NO_LIBC)
fn void*! native_fopen(String filename, String mode) @inline
fn usz! native_fread(CFile file, char[] buffer) @inline
fn void*! native_freopen(void* file, String filename, String mode) @inline
fn void! native_fseek(void* file, isz offset, Seek seek_mode) @inline
fn usz! native_ftell(CFile file) @inline
fn usz! native_fwrite(CFile file, char[] buffer) @inline
fn void! native_remove(String filename) @inline
macro Path! native_temp_directory(Allocator allocator = allocator::heap())
std::io::os @if(env::POSIX)
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
fn void! native_rmtree(Path dir)
std::io::os @if(env::WIN32)
fn PathList! native_ls(Path dir, bool no_dirs, bool no_symlinks, String mask, Allocator allocator)
fn void! native_rmtree(Path path)
std::io::path
enum MkdirPermissions
enum PathEnv
fault PathResult
struct Path (Printable)
fn Path! Path.absolute(self, Allocator allocator = allocator::heap())
fn Path! Path.append(self, String filename, Allocator allocator = allocator::heap())
fn ZString Path.as_zstr(self)
fn String Path.basename(self)
fn String Path.dirname(self)
fn bool Path.equals(self, Path p2)
fn String! Path.extension(self)
fn void Path.free(self)
fn bool Path.has_suffix(self, String str)
fn bool! Path.is_absolute(self)
fn Path! Path.parent(self)
fn String Path.root_directory(self)
fn String Path.str_view(self) @inline
fn Path! Path.tappend(self, String filename)
fn usz! Path.to_format(&self, Formatter* formatter) @dynamic
fn String Path.to_new_string(&self, Allocator allocator = allocator::heap()) @dynamic
fn String Path.volume_name(self)
fn bool! Path.walk(self, PathWalker w, void* data)
fn void! chdir(Path path)
fn void! delete(Path path)
fn bool exists(Path path)
fn usz! file_size(Path path)
fn Path! getcwd(Allocator allocator = allocator::heap())
fn bool is_dir(Path path)
fn bool is_file(Path path)
macro bool is_posix_separator(char c)
macro bool is_reserved_path_char(char c, PathEnv path_env = DEFAULT_PATH_ENV)
macro bool is_reserved_win32_path_char(char c)
macro bool is_separator(char c, PathEnv path_env = DEFAULT_PATH_ENV)
macro bool is_win32_separator(char c)
fn PathList! ls(Path dir, bool no_dirs = false, bool no_symlinks = false, String mask = "", Allocator allocator = allocator::heap())
fn bool! mkdir(Path path, bool recursive = false, MkdirPermissions permissions = NORMAL)
fn Path! new(String path, Allocator allocator = allocator::heap(), PathEnv path_env = DEFAULT_PATH_ENV)
fn Path! new_posix(String path, Allocator allocator = allocator::heap())
fn Path! new_win32_wstring(WString path, Allocator allocator = allocator::heap())
fn Path! new_windows(String path, Allocator allocator = allocator::heap())
fn String! normalize(String path_str, PathEnv path_env = DEFAULT_PATH_ENV)
fn bool! rmdir(Path path)
fn void! rmtree(Path path)
fn Path! temp_directory(Allocator allocator = allocator::heap())
fn Path! temp_new(String path, PathEnv path_env = DEFAULT_PATH_ENV)
fn Path! tgetcwd()
std::math
enum RoundingMode : int
fault MathError
fault MatrixError
fn int128 __ashlti3(int128 a, uint b) @extern("__ashlti3") @weak @nostrip
fn int128 __ashrti3(int128 a, uint b) @extern("__ashrti3") @weak @nostrip
fn int128 __divti3(int128 a, int128 b) @extern("__divti3") @weak @nostrip
fn int128 __fixdfti(double a) @weak @extern("__fixdfti") @nostrip
fn int128 __fixsfti(float a) @weak @extern("__fixsfti") @nostrip
fn uint128 __fixunsdfti(double a) @weak @extern("__fixunsdfti") @nostrip
fn uint128 __fixunssfti(float a) @weak @extern("__fixunssfti") @nostrip
fn double __floattidf(int128 a) @extern("__floattidf") @weak @nostrip
fn float __floattisf(int128 a) @extern("__floattisf") @weak @nostrip
fn double __floatuntidf(uint128 a) @extern("__floatuntidf") @weak @nostrip
fn float __floatuntisf(uint128 a) @extern("__floatuntisf") @weak @nostrip
fn uint128 __lshrti3(uint128 a, uint b) @extern("__lshrti3") @weak @nostrip
fn int128 __modti3(int128 a, int128 b) @extern("__modti3") @weak @nostrip
fn int128 __multi3(int128 a, int128 b) @extern("__multi3") @weak @nostrip
fn double __roundeven(double d) @extern("roundeven") @weak @nostrip
fn float __roundevenf(float f) @extern("roundevenf") @weak @nostrip
fn uint128 __udivti3(uint128 n, uint128 d) @extern("__udivti3") @weak @nostrip
fn uint128 __umodti3(uint128 n, uint128 d) @extern("__umodti3") @weak @nostrip
fn double _frexp(double x, int* e)
fn float _frexpf(float x, int* e)
macro abs(x)
macro acos(x)
macro acosh(x)
macro asin(x)
macro asinh(x)
macro atan(x)
macro atan2(x, y)
macro atanh(x)
macro bool bool[<*>].and(bool[<*>] x)
macro bool[<*>] bool[<*>].comp_eq(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_ge(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_gt(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_le(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_lt(bool[<*>] x, bool[<*>] y)
macro bool[<*>] bool[<*>].comp_ne(bool[<*>] x, bool[<*>] y)
macro bool bool[<*>].max(bool[<*>] x)
macro bool bool[<*>].min(bool[<*>] x)
macro bool bool[<*>].or(bool[<*>] x)
macro bool bool[<*>].product(bool[<*>] x)
macro bool bool[<*>].sum(bool[<*>] x)
macro bool bool[<*>].xor(bool[<*>] x)
macro ceil(x)
macro bool char.is_even(char x)
macro bool char.is_odd(char x)
macro char! char.overflow_add(char x, char y)
macro char! char.overflow_mul(char x, char y)
macro char! char.overflow_sub(char x, char y)
macro char char.sat_add(char x, char y)
macro char char.sat_mul(char x, char y)
macro char char.sat_shl(char x, char y)
macro char char.sat_sub(char x, char y)
macro char char[<*>].and(char[<*>] x)
macro bool[<*>] char[<*>].comp_eq(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_ge(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_gt(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_le(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_lt(char[<*>] x, char[<*>] y)
macro bool[<*>] char[<*>].comp_ne(char[<*>] x, char[<*>] y)
macro char char[<*>].max(char[<*>] x)
macro char char[<*>].min(char[<*>] x)
macro char char[<*>].or(char[<*>] x)
macro char char[<*>].product(char[<*>] x)
macro char char[<*>].sum(char[<*>] x)
macro char char[<*>].xor(char[<*>] x)
macro clamp(x, lower, upper)
macro copysign(mag, sgn)
macro cos(x)
macro cosec(x)
macro cosech(x)
macro cosh(x)
macro cotan(x)
macro cotanh(x)
macro deg_to_rad(x)
macro double double.ceil(double x)
macro double double.clamp(double x, double lower, double upper)
macro double double.copysign(double mag, double sgn)
macro double double.floor(double x)
macro double double.fma(double a, double b, double c)
macro uint double.high_word(double d)
macro uint double.low_word(double d)
macro double double.muladd(double a, double b, double c)
macro double double.nearbyint(double x)
macro double double.pow(double x, exp)
macro double double.rint(double x)
macro double double.round(double x)
macro double double.roundeven(double x)
macro double double.trunc(double x)
macro double[<*>] double[<*>].ceil(double[<*>] x)
macro double[<*>] double[<*>].clamp(double[<*>] x, double[<*>] lower, double[<*>] upper)
macro bool[<*>] double[<*>].comp_eq(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_ge(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_gt(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_le(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_lt(double[<*>] x, double[<*>] y)
macro bool[<*>] double[<*>].comp_ne(double[<*>] x, double[<*>] y)
macro double[<*>] double[<*>].copysign(double[<*>] mag, double[<*>] sgn)
macro double double[<*>].distance(double[<*>] x, double[<*>] y)
macro double double[<*>].dot(double[<*>] x, double[<*>] y)
macro bool double[<*>].equals(double[<*>] x, double[<*>] y)
macro double[<*>] double[<*>].floor(double[<*>] x)
macro double[<*>] double[<*>].fma(double[<*>] a, double[<*>] b, double[<*>] c)
macro double double[<*>].length(double[<*>] x)
macro double[<*>] double[<*>].lerp(double[<*>] x, double[<*>] y, double amount)
macro double double[<*>].max(double[<*>] x)
macro double double[<*>].min(double[<*>] x)
macro double[<*>] double[<*>].nearbyint(double[<*>] x)
macro double[<*>] double[<*>].normalize(double[<*>] x)
macro double[<*>] double[<*>].pow(double[<*>] x, exp)
macro double double[<*>].product(double[<*>] x, double start = 1.0)
macro double[<*>] double[<*>].reflect(double[<*>] x, double[<*>] y)
macro double[<*>] double[<*>].rint(double[<*>] x)
macro double[<*>] double[<*>].round(double[<*>] x)
macro double[<*>] double[<*>].roundeven(double[<*>] x)
macro double double[<*>].sum(double[<*>] x, double start = 0.0)
macro double[<*>] double[<*>].trunc(double[<*>] x)
macro exp(x)
macro exp2(x)
macro float float.ceil(float x)
macro float float.clamp(float x, float lower, float upper)
macro float float.copysign(float mag, float sgn)
macro float float.floor(float x)
macro float float.fma(float a, float b, float c)
macro float float.muladd(float a, float b, float c)
macro float float.nearbyint(float x)
macro float float.pow(float x, exp)
macro float float.rint(float x)
macro float float.round(float x)
macro float float.roundeven(float x)
macro float float.trunc(float x)
macro uint float.word(float d)
macro float[<*>] float[<*>].ceil(float[<*>] x)
macro float[<*>] float[<*>].clamp(float[<*>] x, float[<*>] lower, float[<*>] upper)
macro bool[<*>] float[<*>].comp_eq(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_ge(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_gt(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_le(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_lt(float[<*>] x, float[<*>] y)
macro bool[<*>] float[<*>].comp_ne(float[<*>] x, float[<*>] y)
macro float[<*>] float[<*>].copysign(float[<*>] mag, float[<*>] sgn)
macro float float[<*>].distance(float[<*>] x, float[<*>] y)
macro float float[<*>].dot(float[<*>] x, float[<*>] y)
macro bool float[<*>].equals(float[<*>] x, float[<*>] y)
macro float[<*>] float[<*>].floor(float[<*>] x)
macro float[<*>] float[<*>].fma(float[<*>] a, float[<*>] b, float[<*>] c)
macro float float[<*>].length(float[<*>] x)
macro float[<*>] float[<*>].lerp(float[<*>] x, float[<*>] y, float amount)
macro float float[<*>].max(float[<*>] x)
macro float float[<*>].min(float[<*>] x)
macro float[<*>] float[<*>].nearbyint(float[<*>] x)
macro float[<*>] float[<*>].normalize(float[<*>] x)
macro float[<*>] float[<*>].pow(float[<*>] x, exp)
macro float float[<*>].product(float[<*>] x, float start = 1.0)
macro float[<*>] float[<*>].reflect(float[<*>] x, float[<*>] y)
macro float[<*>] float[<*>].rint(float[<*>] x)
macro float[<*>] float[<*>].round(float[<*>] x)
macro float[<*>] float[<*>].roundeven(float[<*>] x)
macro float float[<*>].sum(float[<*>] x, float start = 0.0)
macro float[<*>] float[<*>].trunc(float[<*>] x)
macro floor(x)
macro fma(a, b, c)
macro frexp(x, int* e)
macro hypot(x, y)
macro bool ichar.is_even(ichar x)
macro bool ichar.is_odd(ichar x)
macro ichar! ichar.overflow_add(ichar x, ichar y)
macro ichar! ichar.overflow_mul(ichar x, ichar y)
macro ichar! ichar.overflow_sub(ichar x, ichar y)
macro ichar ichar.sat_add(ichar x, ichar y)
macro ichar ichar.sat_mul(ichar x, ichar y)
macro ichar ichar.sat_shl(ichar x, ichar y)
macro ichar ichar.sat_sub(ichar x, ichar y)
macro ichar ichar[<*>].and(ichar[<*>] x)
macro bool[<*>] ichar[<*>].comp_eq(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_ge(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_gt(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_le(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_lt(ichar[<*>] x, ichar[<*>] y)
macro bool[<*>] ichar[<*>].comp_ne(ichar[<*>] x, ichar[<*>] y)
macro ichar ichar[<*>].max(ichar[<*>] x)
macro ichar ichar[<*>].min(ichar[<*>] x)
macro ichar ichar[<*>].or(ichar[<*>] x)
macro ichar ichar[<*>].product(ichar[<*>] x)
macro ichar ichar[<*>].sum(ichar[<*>] x)
macro ichar ichar[<*>].xor(ichar[<*>] x)
macro bool int.is_even(int x)
macro bool int.is_odd(int x)
macro int! int.overflow_add(int x, int y)
macro int! int.overflow_mul(int x, int y)
macro int! int.overflow_sub(int x, int y)
macro int int.sat_add(int x, int y)
macro int int.sat_mul(int x, int y)
macro int int.sat_shl(int x, int y)
macro int int.sat_sub(int x, int y)
macro bool int128.is_even(int128 x)
macro bool int128.is_odd(int128 x)
macro int128! int128.overflow_add(int128 x, int128 y)
macro int128! int128.overflow_mul(int128 x, int128 y)
macro int128! int128.overflow_sub(int128 x, int128 y)
macro int128 int128.sat_add(int128 x, int128 y)
macro int128 int128.sat_mul(int128 x, int128 y)
macro int128 int128.sat_shl(int128 x, int128 y)
macro int128 int128.sat_sub(int128 x, int128 y)
macro int128 int128[<*>].and(int128[<*>] x)
macro bool[<*>] int128[<*>].comp_eq(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_ge(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_gt(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_le(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_lt(int128[<*>] x, int128[<*>] y)
macro bool[<*>] int128[<*>].comp_ne(int128[<*>] x, int128[<*>] y)
macro int128 int128[<*>].max(int128[<*>] x)
macro int128 int128[<*>].min(int128[<*>] x)
macro int128 int128[<*>].or(int128[<*>] x)
macro int128 int128[<*>].product(int128[<*>] x)
macro int128 int128[<*>].sum(int128[<*>] x)
macro int128 int128[<*>].xor(int128[<*>] x)
macro int int[<*>].and(int[<*>] x)
macro bool[<*>] int[<*>].comp_eq(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_ge(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_gt(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_le(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_lt(int[<*>] x, int[<*>] y)
macro bool[<*>] int[<*>].comp_ne(int[<*>] x, int[<*>] y)
macro int int[<*>].max(int[<*>] x)
macro int int[<*>].min(int[<*>] x)
macro int int[<*>].or(int[<*>] x)
macro int int[<*>].product(int[<*>] x)
macro int int[<*>].sum(int[<*>] x)
macro int int[<*>].xor(int[<*>] x)
macro bool is_even(x)
macro bool is_finite(x)
macro is_inf(x)
macro is_nan(x)
macro bool is_odd(x)
macro bool is_power_of_2(x)
macro ln(x)
macro log(x, base)
macro log10(x)
macro log2(x)
macro bool long.is_even(long x)
macro bool long.is_odd(long x)
macro long! long.overflow_add(long x, long y)
macro long! long.overflow_mul(long x, long y)
macro long! long.overflow_sub(long x, long y)
macro long long.sat_add(long x, long y)
macro long long.sat_mul(long x, long y)
macro long long.sat_shl(long x, long y)
macro long long.sat_sub(long x, long y)
macro long long[<*>].and(long[<*>] x)
macro bool[<*>] long[<*>].comp_eq(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_ge(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_gt(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_le(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_lt(long[<*>] x, long[<*>] y)
macro bool[<*>] long[<*>].comp_ne(long[<*>] x, long[<*>] y)
macro long long[<*>].max(long[<*>] x)
macro long long[<*>].min(long[<*>] x)
macro long long[<*>].or(long[<*>] x)
macro long long[<*>].product(long[<*>] x)
macro long long[<*>].sum(long[<*>] x)
macro long long[<*>].xor(long[<*>] x)
macro max(x, y, ...)
macro min(x, y, ...)
macro muladd(a, b, c)
macro nearbyint(x)
macro next_power_of_2(x)
macro pow(x, exp)
macro rint(x)
macro round(x)
macro round_to_decimals(x, int decimal_places)
macro roundeven(x)
macro double scalbn(double x, int n)
macro sec(x)
macro sech(x)
macro select(bool[<*>] mask, then_value, else_value)
macro bool short.is_even(short x)
macro bool short.is_odd(short x)
macro short! short.overflow_add(short x, short y)
macro short! short.overflow_mul(short x, short y)
macro short! short.overflow_sub(short x, short y)
macro short short.sat_add(short x, short y)
macro short short.sat_mul(short x, short y)
macro short short.sat_shl(short x, short y)
macro short short.sat_sub(short x, short y)
macro short short[<*>].and(short[<*>] x)
macro bool[<*>] short[<*>].comp_eq(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_ge(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_gt(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_le(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_lt(short[<*>] x, short[<*>] y)
macro bool[<*>] short[<*>].comp_ne(short[<*>] x, short[<*>] y)
macro short short[<*>].max(short[<*>] x)
macro short short[<*>].min(short[<*>] x)
macro short short[<*>].or(short[<*>] x)
macro short short[<*>].product(short[<*>] x)
macro short short[<*>].sum(short[<*>] x)
macro short short[<*>].xor(short[<*>] x)
macro sign(x)
macro int signbit(x)
macro sin(x)
macro sincos(x, y)
macro sinh(x)
macro sqr(x)
macro sqrt(x)
macro tan(x)
macro tanh(x)
macro trunc(x)
macro bool uint.is_even(uint x)
macro bool uint.is_odd(uint x)
macro uint! uint.overflow_add(uint x, uint y)
macro uint! uint.overflow_mul(uint x, uint y)
macro uint! uint.overflow_sub(uint x, uint y)
macro uint uint.sat_add(uint x, uint y)
macro uint uint.sat_mul(uint x, uint y)
macro uint uint.sat_shl(uint x, uint y)
macro uint uint.sat_sub(uint x, uint y)
macro bool uint128.is_even(uint128 x)
macro bool uint128.is_odd(uint128 x)
macro uint128! uint128.overflow_add(uint128 x, uint128 y)
macro uint128! uint128.overflow_mul(uint128 x, uint128 y)
macro uint128! uint128.overflow_sub(uint128 x, uint128 y)
macro uint128 uint128.sat_add(uint128 x, uint128 y)
macro uint128 uint128.sat_mul(uint128 x, uint128 y)
macro uint128 uint128.sat_shl(uint128 x, uint128 y)
macro uint128 uint128.sat_sub(uint128 x, uint128 y)
macro uint128 uint128[<*>].and(uint128[<*>] x)
macro bool[<*>] uint128[<*>].comp_eq(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_ge(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_gt(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_le(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_lt(uint128[<*>] x, uint128[<*>] y)
macro bool[<*>] uint128[<*>].comp_ne(uint128[<*>] x, uint128[<*>] y)
macro uint128 uint128[<*>].max(uint128[<*>] x)
macro uint128 uint128[<*>].min(uint128[<*>] x)
macro uint128 uint128[<*>].or(uint128[<*>] x)
macro uint128 uint128[<*>].product(uint128[<*>] x)
macro uint128 uint128[<*>].sum(uint128[<*>] x)
macro uint128 uint128[<*>].xor(uint128[<*>] x)
macro uint uint[<*>].and(uint[<*>] x)
macro bool[<*>] uint[<*>].comp_eq(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_ge(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_gt(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_le(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_lt(uint[<*>] x, uint[<*>] y)
macro bool[<*>] uint[<*>].comp_ne(uint[<*>] x, uint[<*>] y)
macro uint uint[<*>].max(uint[<*>] x)
macro uint uint[<*>].min(uint[<*>] x)
macro uint uint[<*>].or(uint[<*>] x)
macro uint uint[<*>].product(uint[<*>] x)
macro uint uint[<*>].sum(uint[<*>] x)
macro uint uint[<*>].xor(uint[<*>] x)
macro bool ulong.is_even(ulong x)
macro bool ulong.is_odd(ulong x)
macro ulong! ulong.overflow_add(ulong x, ulong y)
macro ulong! ulong.overflow_mul(ulong x, ulong y)
macro ulong! ulong.overflow_sub(ulong x, ulong y)
macro ulong ulong.sat_add(ulong x, ulong y)
macro ulong ulong.sat_mul(ulong x, ulong y)
macro ulong ulong.sat_shl(ulong x, ulong y)
macro ulong ulong.sat_sub(ulong x, ulong y)
macro ulong ulong[<*>].and(ulong[<*>] x)
macro bool[<*>] ulong[<*>].comp_eq(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_ge(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_gt(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_le(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_lt(ulong[<*>] x, ulong[<*>] y)
macro bool[<*>] ulong[<*>].comp_ne(ulong[<*>] x, ulong[<*>] y)
macro ulong ulong[<*>].max(ulong[<*>] x)
macro ulong ulong[<*>].min(ulong[<*>] x)
macro ulong ulong[<*>].or(ulong[<*>] x)
macro ulong ulong[<*>].product(ulong[<*>] x)
macro ulong ulong[<*>].sum(ulong[<*>] x)
macro ulong ulong[<*>].xor(ulong[<*>] x)
macro bool ushort.is_even(ushort x)
macro bool ushort.is_odd(ushort x)
macro ushort! ushort.overflow_add(ushort x, ushort y)
macro ushort! ushort.overflow_mul(ushort x, ushort y)
macro ushort! ushort.overflow_sub(ushort x, ushort y)
macro ushort ushort.sat_add(ushort x, ushort y)
macro ushort ushort.sat_mul(ushort x, ushort y)
macro ushort ushort.sat_shl(ushort x, ushort y)
macro ushort ushort.sat_sub(ushort x, ushort y)
macro ushort ushort[<*>].and(ushort[<*>] x)
macro bool[<*>] ushort[<*>].comp_eq(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_ge(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_gt(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_le(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_lt(ushort[<*>] x, ushort[<*>] y)
macro bool[<*>] ushort[<*>].comp_ne(ushort[<*>] x, ushort[<*>] y)
macro ushort ushort[<*>].max(ushort[<*>] x)
macro ushort ushort[<*>].min(ushort[<*>] x)
macro ushort ushort[<*>].or(ushort[<*>] x)
macro ushort ushort[<*>].product(ushort[<*>] x)
macro ushort ushort[<*>].sum(ushort[<*>] x)
macro ushort ushort[<*>].xor(ushort[<*>] x)
std::math::complex(<Real>)
macro Complex Complex.add(self, Complex b)
macro Complex Complex.add_each(self, Real b)
macro Complex Complex.div(self, Complex b)
macro Complex Complex.mul(self, Complex b)
macro Complex Complex.scale(self, Real s)
macro Complex Complex.sub(self, Complex b)
macro Complex Complex.sub_each(self, Real b)
std::math::easing
fn float back_in(float t, float b, float c, float d, float s = 1.70158f) @inline
fn float back_inout(float t, float b, float c, float d, float s = 1.70158f) @inline
fn float back_out(float t, float b, float c, float d, float s = 1.70158f) @inline
fn float bounce_in(float t, float b, float c, float d) @inline
fn float bounce_inout(float t, float b, float c, float d) @inline
fn float bounce_out(float t, float b, float c, float d) @inline
fn float circ_in(float t, float b, float c, float d) @inline
fn float circ_inout(float t, float b, float c, float d) @inline
fn float circ_out(float t, float b, float c, float d) @inline
fn float cubic_in(float t, float b, float c, float d) @inline
fn float cubic_inout(float t, float b, float c, float d) @inline
fn float cubic_out(float t, float b, float c, float d) @inline
fn float elastic_in(float t, float b, float c, float d) @inline
fn float elastic_inout(float t, float b, float c, float d) @inline
fn float elastic_out(float t, float b, float c, float d) @inline
fn float expo_in(float t, float b, float c, float d) @inline
fn float expo_inout(float t, float b, float c, float d) @inline
fn float expo_out(float t, float b, float c, float d) @inline
fn float linear_in(float t, float b, float c, float d) @inline
fn float linear_inout(float t, float b, float c, float d) @inline
fn float linear_none(float t, float b, float c, float d) @inline
fn float linear_out(float t, float b, float c, float d) @inline
fn float quad_in(float t, float b, float c, float d) @inline
fn float quad_inout(float t, float b, float c, float d) @inline
fn float quad_out(float t, float b, float c, float d) @inline
fn float sine_in(float t, float b, float c, float d) @inline
fn float sine_inout(float t, float b, float c, float d) @inline
fn float sine_out(float t, float b, float c, float d) @inline
std::math::matrix(<Real>)
struct Matrix2x2
struct Matrix3x3
struct Matrix4x4
fn Matrix2x2 Matrix2x2.add(&self, Matrix2x2 mat2)
fn Matrix2x2 Matrix2x2.adjoint(&self)
fn Real[<2>] Matrix2x2.apply(&self, Real[<2>] vec)
fn Matrix2x2 Matrix2x2.component_mul(&self, Real s)
fn Real Matrix2x2.determinant(&self)
fn Matrix2x2! Matrix2x2.inverse(&self)
fn Matrix2x2 Matrix2x2.mul(&self, Matrix2x2 b)
fn Matrix2x2 Matrix2x2.sub(&self, Matrix2x2 mat2)
fn Real Matrix2x2.trace(&self)
fn Matrix2x2 Matrix2x2.transpose(&self)
fn Matrix3x3 Matrix3x3.add(&self, Matrix3x3 mat2)
fn Matrix3x3 Matrix3x3.adjoint(&self)
fn Real[<3>] Matrix3x3.apply(&self, Real[<3>] vec)
fn Matrix3x3 Matrix3x3.component_mul(&self, Real s)
fn Real Matrix3x3.determinant(&self)
fn Matrix3x3! Matrix3x3.inverse(&self)
fn Matrix3x3 Matrix3x3.mul(&self, Matrix3x3 b)
fn Matrix3x3 Matrix3x3.rotate(&self, Real r)
fn Matrix3x3 Matrix3x3.scale(&self, Real[<2>] v)
fn Matrix3x3 Matrix3x3.sub(&self, Matrix3x3 mat2)
fn Real Matrix3x3.trace(&self)
fn Matrix3x3 Matrix3x3.translate(&self, Real[<2>] v)
fn Matrix3x3 Matrix3x3.transpose(&self)
fn Matrix4x4 Matrix4x4.add(&self, Matrix4x4 mat2)
fn Matrix4x4 Matrix4x4.adjoint(&self)
fn Real[<4>] Matrix4x4.apply(&self, Real[<4>] vec)
fn Matrix4x4 Matrix4x4.component_mul(&self, Real s)
fn Real Matrix4x4.determinant(&self)
fn Matrix4x4! Matrix4x4.inverse(&self)
fn Matrix4x4 Matrix4x4.mul(Matrix4x4* a, Matrix4x4 b)
fn Matrix4x4 Matrix4x4.rotate_x(&self, Real r)
fn Matrix4x4 Matrix4x4.rotate_y(&self, Real r)
fn Matrix4x4 Matrix4x4.rotate_z(&self, Real r)
fn Matrix4x4 Matrix4x4.scale(&self, Real[<3>] v)
fn Matrix4x4 Matrix4x4.sub(&self, Matrix4x4 mat2)
fn Real Matrix4x4.trace(&self)
fn Matrix4x4 Matrix4x4.translate(&self, Real[<3>] v)
fn Matrix4x4 Matrix4x4.transpose(&self)
fn Matrix4x4 ortho(Real left, Real right, Real top, Real bottom, Real near, Real far)
fn Matrix4x4 perspective(Real fov, Real aspect_ratio, Real near, Real far)
std::math::nolibc
macro double __math_oflow(ulong sign)
macro float __math_oflowf(uint sign)
macro double __math_uflow(ulong sign)
macro float __math_uflowf(uint sign)
macro __math_xflow(sign, v)
macro force_eval_add(x, v)
std::math::nolibc @if(env::NO_LIBC)
fn double __cos(double x, double y) @extern("__cos") @weak @nostrip
fn float __cosdf(double x) @extern("__cosdf") @weak @nostrip
fn int __rem_pio2(double x, double *y)
fn int __rem_pio2_large(double* x, double* y, int e0, int nx, int prec)
fn int __rem_pio2f(float x, double *y)
fn double __sin(double x, double y, int iy) @extern("__sin") @weak @nostrip
fn float __sindf(double x) @extern("__sindf") @weak @nostrip
fn double __tan(double x, double y, int odd) @extern("__tan") @weak @nostrip
fn float __tandf(double x, int odd) @extern("__tandf") @weak @nostrip
fn double _atan(double x) @weak @extern("atan") @nostrip
fn double _atan2(double y, double x) @weak @extern("atan2") @nostrip
fn float _atan2f(float y, float x) @weak @extern("atan2f") @nostrip
fn float _atanf(float x) @weak @extern("atanf") @nostrip
fn double _ceil(double x) @weak @extern("ceil") @nostrip
fn float _ceilf(float x) @weak @extern("ceilf") @nostrip
fn double _cos(double x) @weak @nostrip
fn float _cosf(float x) @extern("cosf") @weak @nostrip
fn double _exp2(double x) @extern("exp2") @weak @nostrip
fn float _exp2f(float x) @extern("exp2f") @weak @nostrip
fn double _floor(double x) @weak @extern("floor") @nostrip
fn float _floorf(float x) @weak @extern("floorf") @nostrip
fn double _round(double x) @extern("round") @weak @nostrip
fn float _roundf(float x) @extern("roundf") @weak @nostrip
fn double _scalbn(double x, int n) @weak @extern("scalbn") @nostrip
fn float _sinf(float x) @weak @extern("sinf") @nostrip
fn double _trunc(double x) @weak @extern("trunc") @nostrip
fn float _truncf(float x) @weak @extern("truncf") @nostrip
fn double pow_broken(double x, double y) @extern("pow") @weak @nostrip
fn float powf_broken(float x, float f) @extern("powf") @weak @nostrip
fn double sin(double x) @extern("sin") @weak @nostrip
fn void sincos(double x, double *sin, double *cos) @extern("sincos") @weak @nostrip
fn double sincos_broken(double x) @extern("sincos") @weak @nostrip
fn void sincosf(float x, float *sin, float *cos) @extern("sincosf") @weak @nostrip
fn double tan(double x) @extern("tan") @weak @nostrip
fn float tanf(float x) @extern("tanf") @weak @nostrip
std::math::quaternion(<Real>)
macro Quaternion Quaternion.add(Quaternion a, Quaternion b)
macro Quaternion Quaternion.add_each(Quaternion a, Real b)
fn Quaternion Quaternion.invert(q)
macro Real Quaternion.length(Quaternion q)
macro Quaternion Quaternion.lerp(Quaternion q1, Quaternion q2, Real amount)
fn Quaternion Quaternion.mul(a, Quaternion b)
fn Quaternion Quaternion.nlerp(Quaternion q1, Quaternion q2, Real amount)
macro Quaternion Quaternion.normalize(Quaternion q)
macro Quaternion Quaternion.scale(Quaternion a, Real s)
fn Quaternion Quaternion.slerp(q1, Quaternion q2, Real amount)
macro Quaternion Quaternion.sub(Quaternion a, Quaternion b)
macro Quaternion Quaternion.sub_each(Quaternion a, Real b)
macro Matrix4 Quaternion.to_matrix(Quaternion* q)
macro Matrix4f Quaternion.to_matrixf(Quaternion* q)
std::math::random
distinct Lcg128Random (Random) = uint128;
distinct Lcg16Random (Random) = ushort;
distinct Lcg32Random (Random) = uint;
distinct Lcg64Random (Random) = ulong;
distinct Mcg128Random (Random) = uint128;
distinct Mcg16Random (Random) = ushort;
distinct Mcg32Random (Random) = uint;
distinct Mcg64Random (Random) = ulong;
distinct Pcg128Random (Random) = uint128;
distinct Pcg16Random (Random) = ushort;
distinct Pcg32Random (Random) = uint;
distinct Pcg64Random (Random) = ulong;
distinct Sfc128Random (Random) = uint128[4];
distinct Sfc16Random (Random) = ushort[4];
distinct Sfc32Random (Random) = uint[4];
distinct Sfc64Random (Random) = ulong[4];
distinct Sfc8Random (Random) = char[4];
distinct SimpleRandom (Random) = ulong;
interface Random
struct Msws128Random (Random)
struct Msws16Random (Random)
struct Msws32Random (Random)
struct Msws64Random (Random)
struct Msws8Random (Random)
fn void Msws16Random.set_seed(&self, char[] input) @dynamic
fn void Msws32Random.set_seed(&self, char[] input) @dynamic
fn void Msws64Random.set_seed(&self, char[] input) @dynamic
fn void Msws8Random.set_seed(&self, char[] input) @dynamic
fn void Pcg128Random.set_seed(&self, char[] input) @dynamic
fn void Sfc128Random.set_seed(&self, char[] input) @dynamic
fn void Sfc16Random.set_seed(&self, char[] input) @dynamic
fn void Sfc32Random.set_seed(&self, char[] input) @dynamic
fn void Sfc64Random.set_seed(&self, char[] input) @dynamic
fn void Sfc8Random.set_seed(&self, char[] input) @dynamic
fn char[8 * 4] entropy()
macro ushort @char_to_short(#function)
macro ulong @int_to_long(#function)
macro uint128 @long_to_int128(#function)
macro @random_value_to_bytes(#function, char[] bytes)
macro uint @short_to_int(#function)
fn char Lcg128Random.next_byte(&self) @dynamic
fn void Lcg128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Lcg128Random.next_int(&self) @dynamic
fn uint128 Lcg128Random.next_int128(&self) @dynamic
fn ulong Lcg128Random.next_long(&self) @dynamic
fn ushort Lcg128Random.next_short(&self) @dynamic
fn void Lcg128Random.set_seed(&self, char[] input) @dynamic
fn char Lcg16Random.next_byte(&self) @dynamic
fn void Lcg16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Lcg16Random.next_int(&self) @dynamic
fn uint128 Lcg16Random.next_int128(&self) @dynamic
fn ulong Lcg16Random.next_long(&self) @dynamic
fn ushort Lcg16Random.next_short(&self) @dynamic
fn void Lcg16Random.set_seed(&self, char[] seed) @dynamic
fn char Lcg32Random.next_byte(&self) @dynamic
fn void Lcg32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Lcg32Random.next_int(&self) @dynamic
fn uint128 Lcg32Random.next_int128(&self) @dynamic
fn ulong Lcg32Random.next_long(&self) @dynamic
fn ushort Lcg32Random.next_short(&self) @dynamic
fn void Lcg32Random.set_seed(&self, char[] seed) @dynamic
fn char Lcg64Random.next_byte(&self) @dynamic
fn void Lcg64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Lcg64Random.next_int(&self) @dynamic
fn uint128 Lcg64Random.next_int128(&self) @dynamic
fn ulong Lcg64Random.next_long(&self) @dynamic
fn ushort Lcg64Random.next_short(&self) @dynamic
fn void Lcg64Random.set_seed(&self, char[] seed) @dynamic
fn char Mcg128Random.next_byte(&self) @dynamic
fn void Mcg128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Mcg128Random.next_int(&self) @dynamic
fn uint128 Mcg128Random.next_int128(&self) @dynamic
fn ulong Mcg128Random.next_long(&self) @dynamic
fn ushort Mcg128Random.next_short(&self) @dynamic
fn void Mcg128Random.set_seed(&self, char[] seed) @dynamic
fn char Mcg16Random.next_byte(&self) @dynamic
fn void Mcg16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Mcg16Random.next_int(&self) @dynamic
fn uint128 Mcg16Random.next_int128(&self) @dynamic
fn ulong Mcg16Random.next_long(&self) @dynamic
fn ushort Mcg16Random.next_short(&self) @dynamic
fn void Mcg16Random.set_seed(&self, char[] seed) @dynamic
fn char Mcg32Random.next_byte(&self) @dynamic
fn void Mcg32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Mcg32Random.next_int(&self) @dynamic
fn uint128 Mcg32Random.next_int128(&self) @dynamic
fn ulong Mcg32Random.next_long(&self) @dynamic
fn ushort Mcg32Random.next_short(&self) @dynamic
fn void Mcg32Random.set_seed(&self, char[] seed) @dynamic
fn char Mcg64Random.next_byte(&self) @dynamic
fn void Mcg64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Mcg64Random.next_int(&self) @dynamic
fn uint128 Mcg64Random.next_int128(&self) @dynamic
fn ulong Mcg64Random.next_long(&self) @dynamic
fn ushort Mcg64Random.next_short(&self) @dynamic
fn void Mcg64Random.set_seed(&self, char[] seed) @dynamic
fn char Msws128Random.next_byte(&self) @dynamic
fn void Msws128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws128Random.next_int(&self) @dynamic
fn uint128 Msws128Random.next_int128(&self) @dynamic
fn ulong Msws128Random.next_long(&self) @dynamic
fn ushort Msws128Random.next_short(&self) @dynamic
fn void Msws128Random.set_seed(&self, char[] input) @dynamic
fn char Msws16Random.next_byte(&self) @dynamic
fn void Msws16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws16Random.next_int(&self) @dynamic
fn uint128 Msws16Random.next_int128(&self) @dynamic
fn ulong Msws16Random.next_long(&self) @dynamic
fn ushort Msws16Random.next_short(&self) @dynamic
fn char Msws32Random.next_byte(&self) @dynamic
fn void Msws32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws32Random.next_int(&self) @dynamic
fn uint128 Msws32Random.next_int128(&self) @dynamic
fn ulong Msws32Random.next_long(&self) @dynamic
fn ushort Msws32Random.next_short(&self) @dynamic
fn char Msws64Random.next_byte(&self) @dynamic
fn void Msws64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws64Random.next_int(&self) @dynamic
fn uint128 Msws64Random.next_int128(&self) @dynamic
fn ulong Msws64Random.next_long(&self) @dynamic
fn ushort Msws64Random.next_short(&self) @dynamic
fn char Msws8Random.next_byte(&self) @dynamic
fn void Msws8Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Msws8Random.next_int(&self) @dynamic
fn uint128 Msws8Random.next_int128(&self) @dynamic
fn ulong Msws8Random.next_long(&self) @dynamic
fn ushort Msws8Random.next_short(&self) @dynamic
fn char Pcg128Random.next_byte(&self) @dynamic
fn void Pcg128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Pcg128Random.next_int(&self) @dynamic
fn uint128 Pcg128Random.next_int128(&self) @dynamic
fn ulong Pcg128Random.next_long(&self) @dynamic
fn ushort Pcg128Random.next_short(&self) @dynamic
fn char Pcg16Random.next_byte(&self) @dynamic
fn void Pcg16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Pcg16Random.next_int(&self) @dynamic
fn uint128 Pcg16Random.next_int128(&self) @dynamic
fn ulong Pcg16Random.next_long(&self) @dynamic
fn ushort Pcg16Random.next_short(&self) @dynamic
fn void Pcg16Random.set_seed(&self, char[] input) @dynamic
fn char Pcg32Random.next_byte(&self) @dynamic
fn void Pcg32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Pcg32Random.next_int(&self) @dynamic
fn uint128 Pcg32Random.next_int128(&self) @dynamic
fn ulong Pcg32Random.next_long(&self) @dynamic
fn ushort Pcg32Random.next_short(&self) @dynamic
fn void Pcg32Random.set_seed(&self, char[] input) @dynamic
fn char Pcg64Random.next_byte(&self) @dynamic
fn void Pcg64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Pcg64Random.next_int(&self) @dynamic
fn uint128 Pcg64Random.next_int128(&self) @dynamic
fn ulong Pcg64Random.next_long(&self) @dynamic
fn ushort Pcg64Random.next_short(&self) @dynamic
fn void Pcg64Random.set_seed(&self, char[] input) @dynamic
fn char Sfc128Random.next_byte(&self) @dynamic
fn void Sfc128Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc128Random.next_int(&self) @dynamic
fn uint128 Sfc128Random.next_int128(&self) @dynamic
fn ulong Sfc128Random.next_long(&self) @dynamic
fn ushort Sfc128Random.next_short(&self) @dynamic
fn char Sfc16Random.next_byte(&self) @dynamic
fn void Sfc16Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc16Random.next_int(&self) @dynamic
fn uint128 Sfc16Random.next_int128(&self) @dynamic
fn ulong Sfc16Random.next_long(&self) @dynamic
fn ushort Sfc16Random.next_short(&seed) @dynamic
fn char Sfc32Random.next_byte(&self) @dynamic
fn void Sfc32Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc32Random.next_int(&sfc) @dynamic
fn uint128 Sfc32Random.next_int128(&self) @dynamic
fn ulong Sfc32Random.next_long(&self) @dynamic
fn ushort Sfc32Random.next_short(&self) @dynamic
fn char Sfc64Random.next_byte(&self) @dynamic
fn void Sfc64Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc64Random.next_int(&self) @dynamic
fn uint128 Sfc64Random.next_int128(&self) @dynamic
fn ulong Sfc64Random.next_long(&self) @dynamic
fn ushort Sfc64Random.next_short(&self) @dynamic
fn char Sfc8Random.next_byte(&self) @dynamic
fn void Sfc8Random.next_bytes(&self, char[] bytes) @dynamic
fn uint Sfc8Random.next_int(&self) @dynamic
fn uint128 Sfc8Random.next_int128(&self) @dynamic
fn ulong Sfc8Random.next_long(&self) @dynamic
fn ushort Sfc8Random.next_short(&self) @dynamic
fn char SimpleRandom.next_byte(&self) @dynamic
fn void SimpleRandom.next_bytes(&self, char[] bytes) @dynamic
fn uint SimpleRandom.next_int(&self) @dynamic
fn uint128 SimpleRandom.next_int128(&self) @dynamic
fn ulong SimpleRandom.next_long(&self) @dynamic
fn ushort SimpleRandom.next_short(&self) @dynamic
fn void SimpleRandom.set_seed(&self, char[] seed) @dynamic
macro bool is_random(random)
macro make_seed($Type, char[] input)
macro int next(random, int max)
macro void next_bool(random)
macro double next_double(random)
macro float next_float(random)
fn int rand(int max) @builtin
macro void seed(random, seed)
macro void seed_entropy(random)
fn void seeder(char[] input, char[] out_buffer)
std::math::vector
macro Vec2.angle(self, Vec2 v2)
macro Vec2.clamp_mag(self, double min, double max)
macro Vec2.distance_sq(self, Vec2 v2)
macro Vec2.length_sq(self)
macro Vec2.rotate(self, double angle)
fn Vec2 Vec2.towards(self, Vec2 target, double max_distance)
macro Vec2.transform(self, Matrix4 mat)
macro Vec2f.angle(self, Vec2f v2)
macro Vec2f.clamp_mag(self, float min, float max)
macro Vec2f.distance_sq(self, Vec2f v2)
macro Vec2f.length_sq(self)
macro Vec2f.rotate(self, float angle)
fn Vec2f Vec2f.towards(self, Vec2f target, float max_distance)
macro Vec2f.transform(self, Matrix4f mat)
fn double Vec3.angle(self, Vec3 v2)
fn Vec3 Vec3.barycenter(self, Vec3 a, Vec3 b, Vec3 c)
macro Vec3.clamp_mag(self, double min, double max)
fn Vec3 Vec3.cross(self, Vec3 v2)
macro Vec3.distance_sq(self, Vec3 v2)
macro Vec3.length_sq(self)
fn Vec3 Vec3.perpendicular(self)
fn Vec3 Vec3.refract(self, Vec3 n, double r)
fn Vec3 Vec3.rotate_axis(self, Vec3 axis, double angle)
fn Vec3 Vec3.rotate_quat(self, Quaternion q)
fn Vec3 Vec3.towards(self, Vec3 target, double max_distance)
fn Vec3 Vec3.transform(self, Matrix4 mat)
fn Vec3 Vec3.unproject(self, Matrix4 projection, Matrix4 view)
fn float Vec3f.angle(self, Vec3f v2)
fn Vec3f Vec3f.barycenter(self, Vec3f a, Vec3f b, Vec3f c)
macro Vec3f.clamp_mag(self, float min, float max)
fn Vec3f Vec3f.cross(self, Vec3f v2)
macro Vec3f.distance_sq(self, Vec3f v2)
macro Vec3f.length_sq(self)
fn Vec3f Vec3f.perpendicular(self)
fn Vec3f Vec3f.refract(self, Vec3f n, float r)
fn Vec3f Vec3f.rotate_axis(self, Vec3f axis, float angle)
fn Vec3f Vec3f.rotate_quat(self, Quaternionf q)
fn Vec3f Vec3f.towards(self, Vec3f target, float max_distance)
fn Vec3f Vec3f.transform(self, Matrix4f mat)
fn Vec3f Vec3f.unproject(self, Matrix4f projection, Matrix4f view)
macro Vec4.clamp_mag(self, double min, double max)
macro Vec4.distance_sq(self, Vec4 v2)
macro Vec4.length_sq(self)
fn Vec4 Vec4.towards(self, Vec4 target, double max_distance)
macro Vec4f.clamp_mag(self, float min, float max)
macro Vec4f.distance_sq(self, Vec4f v2)
macro Vec4f.length_sq(self)
fn Vec4f Vec4f.towards(self, Vec4f target, float max_distance)
fn Matrix4 matrix4_look_at(Vec3 eye, Vec3 target, Vec3 up)
fn Matrix4f matrix4f_look_at(Vec3f eye, Vec3f target, Vec3f up)
fn void ortho_normalize(Vec3f* v1, Vec3f* v2)
fn void ortho_normalized(Vec3* v1, Vec3* v2)
std::net
enum IpProtocol : char (AIFamily ai_family)
fault NetError
struct InetAddress (Printable)
fn bool InetAddress.is_any_local(InetAddress* addr)
fn bool InetAddress.is_link_local(InetAddress* addr)
fn bool InetAddress.is_loopback(InetAddress* addr)
fn bool InetAddress.is_multicast(InetAddress* addr)
fn bool InetAddress.is_multicast_global(InetAddress* addr)
fn bool InetAddress.is_multicast_link_local(InetAddress* addr)
fn bool InetAddress.is_multicast_node_local(InetAddress* addr)
fn bool InetAddress.is_multicast_org_local(InetAddress* addr)
fn bool InetAddress.is_multicast_site_local(InetAddress* addr)
fn bool InetAddress.is_site_local(InetAddress* addr)
fn usz! InetAddress.to_format(InetAddress* addr, Formatter* formatter) @dynamic
fn String InetAddress.to_new_string(InetAddress* addr, Allocator allocator = allocator::heap()) @dynamic
fn AddrInfo*! addrinfo(String host, uint port, AIFamily ai_family, AISockType ai_socktype) @if(os::SUPPORTS_INET)
fn String! int_to_new_ipv4(uint val, Allocator allocator = allocator::heap())
fn String! int_to_temp_ipv4(uint val)
fn InetAddress! ipv4_from_str(String s)
fn uint! ipv4toint(String s)
fn InetAddress! ipv6_from_str(String s)
std::net @if(os::SUPPORTS_INET)
distinct PollEvents = ushort;
distinct PollSubscribes = ushort;
enum SocketOption : char (CInt value)
struct Poll
struct Socket (InStream, OutStream)
macro void @loop_over_ai(AddrInfo* ai; @body(NativeSocket fd, AddrInfo* ai))
fn void! Socket.close(&self) @inline @dynamic
fn void! Socket.destroy(&self) @dynamic
fn bool! Socket.get_broadcast(&self)
fn bool! Socket.get_dontroute(&self)
fn bool! Socket.get_keepalive(&self)
fn bool! Socket.get_oobinline(&self)
fn bool! Socket.get_option(&self, SocketOption option)
fn bool! Socket.get_reuseaddr(&self)
fn usz! Socket.read(&self, char[] bytes) @dynamic
fn char! Socket.read_byte(&self) @dynamic
fn void! Socket.set_broadcast(&self, bool value)
fn void! Socket.set_dontroute(&self, bool value)
fn void! Socket.set_keepalive(&self, bool value)
fn void! Socket.set_oobinline(&self, bool value)
fn void! Socket.set_option(&self, SocketOption option, bool value)
fn void! Socket.set_reuseaddr(&self, bool value)
fn usz! Socket.write(&self, char[] bytes) @dynamic
fn void! Socket.write_byte(&self, char byte) @dynamic
macro Socket new_socket(fd, ai)
fn ulong! poll(Poll[] polls, Duration timeout)
fn ulong! poll_ms(Poll[] polls, long timeout_ms)
std::net::os
distinct AIFamily = CInt;
distinct AIFlags = CInt;
distinct AIProtocol = CInt;
distinct AISockType = CInt;
distinct SockAddrPtr = void*;
struct AddrInfo
std::net::os @if(env::POSIX && SUPPORTS_INET)
distinct NativeSocket = inline Fd;
struct Posix_pollfd
macro void! NativeSocket.close(self)
macro bool NativeSocket.is_non_blocking(self)
macro void! NativeSocket.set_non_blocking(self, bool non_blocking)
fn anyfault convert_error(Errno error)
fn anyfault socket_error()
std::net::os @if(env::WIN32)
distinct NativeSocket = uptr;
macro void! NativeSocket.close(self)
fn void! NativeSocket.set_non_blocking(self, bool non_blocking)
fn anyfault convert_error(WSAError error)
fn anyfault socket_error()
std::net::tcp @if(os::SUPPORTS_INET)
distinct TcpServerSocket = inline Socket;
distinct TcpSocket = inline Socket;
fn TcpSocket! accept(TcpServerSocket* server_socket)
fn TcpSocket! connect(String host, uint port, Duration timeout = 0, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn TcpSocket! connect_async(String host, uint port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn TcpSocket! connect_async_to(AddrInfo* ai, SocketOption... options)
fn TcpSocket! connect_to(AddrInfo* ai, SocketOption... options)
fn TcpServerSocket! listen(String host, uint port, uint backlog, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn TcpServerSocket! listen_to(AddrInfo* ai, uint backlog, SocketOption... options)
std::net::udp @if(os::SUPPORTS_INET)
distinct UdpSocket = inline Socket;
fn UdpSocket! connect(String host, uint port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn UdpSocket! connect_async(String host, uint port, SocketOption... options, IpProtocol ip_protocol = UNSPECIFIED)
fn UdpSocket! connect_async_to(AddrInfo* ai, SocketOption... options)
fn UdpSocket! connect_to(AddrInfo* ai, SocketOption... options)
std::os @if(env::DARWIN)
fn uint num_cpu()
std::os @if(env::LINUX)
fn uint num_cpu()
std::os @if(env::WIN32)
fn uint num_cpu()
std::os::backtrace
fault BacktraceFault
struct Backtrace (Printable)
fn void Backtrace.free(&self)
fn bool Backtrace.has_file(&self)
fn Backtrace* Backtrace.init(&self, uptr offset, String function, String object_file, String file = "", uint line = 0, Allocator allocator)
fn bool Backtrace.is_unknown(&self)
fn usz! Backtrace.to_format(&self, Formatter* formatter) @dynamic
fn void*[] capture_current(void*[] buffer)
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator) @if(!env::NATIVE_STACKTRACE)
std::os::darwin @if(env::DARWIN)
struct Darwin_Dl_info
struct Darwin_segment_command_64
fn String! executable_path(Allocator allocator)
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator)
std::os::env
fn bool clear_var(String name)
fn String! executable_path(Allocator allocator = allocator::heap())
fn Path! get_config_dir(Allocator allocator = allocator::heap())
fn String! get_home_dir(Allocator using = allocator::heap())
fn String! get_var(String name, Allocator allocator = allocator::heap())
fn String! get_var_temp(String name)
fn bool set_var(String name, String value, bool overwrite = true)
std::os::linux @if(env::LINUX)
struct Elf32_Ehdr
struct Elf32_Phdr
struct Elf64_Ehdr
struct Elf64_Phdr
struct Linux_Dl_info
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator)
std::os::macos::cf @if(env::DARWIN) @link(env::DARWIN, “CoreFoundation.framework”)
distinct CFAllocatorContextRef = void*;
distinct CFAllocatorRef = void*;
distinct CFArrayCallBacksRef = void*;
distinct CFArrayRef = void*;
distinct CFMutableArrayRef = void*;
distinct CFTypeRef = void*;
struct CFRange
macro void* CFAllocatorRef.alloc(CFAllocatorRef allocator, usz size)
macro void CFAllocatorRef.dealloc(CFAllocatorRef allocator, void* ptr)
macro usz CFAllocatorRef.get_preferred_size(CFAllocatorRef allocator, usz req_size)
macro void CFAllocatorRef.set_default(CFAllocatorRef allocator)
macro CFAllocatorRef default_allocator()
std::os::macos::objc @if(env::DARWIN) @link(env::DARWIN, “CoreFoundation.framework”)
distinct Class = void*;
distinct Ivar = void*;
distinct Method = void*;
distinct Selector = void*;
fault ObjcFailure
macro bool Class.equals(Class a, Class b)
macro Method Class.method(Class cls, Selector name)
macro ZString Class.name(Class cls)
macro bool Class.responds_to(Class cls, Selector sel)
macro Class Class.superclass(Class cls)
macro bool Selector.equals(Selector a, Selector b)
macro Class! class_by_name(ZString c)
macro Class[] class_get_list(Allocator allocator = allocator::heap())
std::os::posix @if(env::POSIX)
distinct DIRPtr = void*;
distinct Pthread_t = void*;
struct Posix_dirent
struct Posix_spawn_file_actions_t
struct Posix_spawnattr_t
fn CInt backtrace(void** buffer, CInt size)
macro CInt wEXITSTATUS(CInt status)
macro bool wIFCONTINUED(CInt status)
macro bool wIFEXITED(CInt status)
macro bool wIFSIGNALED(CInt status)
macro bool wIFSTOPPED(CInt status)
macro CInt wSTOPSIG(CInt status)
macro CInt wTERMSIG(CInt status)
macro CInt wWCOREDUMP(CInt status)
macro CInt w_EXITCODE(CInt ret, CInt sig)
macro CInt w_STOPCODE(CInt sig)
std::os::process @if(env::WIN32 || env::POSIX)
fault SubProcessResult
struct SubProcess
fn bool SubProcess.destroy(&self)
fn bool! SubProcess.is_running(&self)
fn CInt! SubProcess.join(&self) @if(env::POSIX)
fn CInt! SubProcess.join(&self) @if(env::WIN32)
fn usz! SubProcess.read_stderr(&self, char* buffer, usz size)
fn usz! SubProcess.read_stdout(&self, char* buffer, usz size)
fn File SubProcess.stdout(&self)
fn void! SubProcess.terminate(&self)
fn SubProcess! create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::POSIX)
fn SubProcess! create(String[] command_line, SubProcessOptions options = {}, String[] environment = {}) @if(env::WIN32)
fn String! execute_stdout_to_buffer(char[] buffer, String[] command_line, SubProcessOptions options = {}, String[] environment = {})
std::os::win32
distinct Win32_CRITICAL_SECTION = ulong[5];
enum Win32_ADDRESS_MODE
enum Win32_SYM_TYPE
struct Win32_ADDRESS64
struct Win32_AMD64_CONTEXT @align(16)
struct Win32_ARM64_NT_CONTEXT @align(16)
struct Win32_ARM64_NT_NEON128
struct Win32_FILETIME
struct Win32_GUID
struct Win32_IMAGEHLP_LINE64
struct Win32_IMAGEHLP_MODULE64
struct Win32_IMAGE_DATA_DIRECTORY
struct Win32_IMAGE_FILE_HEADER
struct Win32_IMAGE_NT_HEADERS
struct Win32_IMAGE_OPTIONAL_HEADER64
struct Win32_KDHELP64
struct Win32_M128A @align(16)
struct Win32_MODLOAD_DATA
struct Win32_MODULEINFO
struct Win32_OVERLAPPED
struct Win32_PROCESS_INFORMATION
struct Win32_SECURITY_ATTRIBUTES
struct Win32_STACKFRAME64
struct Win32_STARTUPINFOEXW
struct Win32_STARTUPINFOW
struct Win32_SYMBOL_INFO
struct Win32_SYSTEM_INFO
struct Win32_UNICODE_STRING
struct Win32_XMM_SAVE_AREA32
std::os::win32 @if(env::WIN32)
distinct WSAError = int;
enum Win32_GET_FILEEX_INFO_LEVELS
struct Symbol
struct Win32_FILE_ATTRIBUTE_DATA
struct Win32_WIN32_FIND_DATAW
struct Win32_pollfd
fn Win32_DWORD! load_modules()
fn Backtrace! resolve_backtrace(void* addr, Win32_HANDLE process, Allocator allocator)
fn BacktraceList! symbolize_backtrace(void*[] backtrace, Allocator allocator)
std::sort
macro bool @is_comparer(#cmp, #list)
macro usz @len_from_list(&list)
macro usz binarysearch(list, x, cmp = null) @builtin
macro quicksort(list, cmp = null) @builtin
std::sort::qs(<Type, Comparer>)
fn void qsort(Type list, isz low, isz high, Comparer cmp)
std::thread
distinct ConditionVariable = NativeConditionVariable;
distinct Mutex = NativeMutex;
distinct MutexType = int;
distinct OnceFlag = NativeOnceFlag;
distinct RecursiveMutex = inline Mutex;
distinct Thread = NativeThread;
distinct TimedMutex = inline Mutex;
distinct TimedRecursiveMutex = inline Mutex;
fault ThreadFault
macro void! ConditionVariable.broadcast(&cond)
macro void! ConditionVariable.destroy(&cond)
macro void! ConditionVariable.init(&cond)
macro void! ConditionVariable.signal(&cond)
macro void! ConditionVariable.wait(&cond, Mutex* mutex)
macro void! ConditionVariable.wait_timeout(&cond, Mutex* mutex, ulong timeout)
macro void! Mutex.destroy(&mutex)
macro void! Mutex.init(&mutex)
macro void! Mutex.lock(&mutex)
macro bool Mutex.try_lock(&mutex)
macro void! Mutex.unlock(&mutex)
macro void OnceFlag.call(&flag, OnceFn func)
macro void! RecursiveMutex.init(&mutex)
macro void! Thread.create(&thread, ThreadFn thread_fn, void* arg)
macro void! Thread.detach(thread)
macro bool Thread.equals(thread, Thread other)
macro int! Thread.join(thread)
macro void! TimedMutex.init(&mutex)
macro void! TimedMutex.lock_timeout(&mutex, ulong ms)
macro void! TimedRecursiveMutex.init(&mutex)
macro void! TimedRecursiveMutex.lock_timeout(&mutex, ulong ms)
macro Thread current()
macro void exit(int result)
macro void! sleep(Duration d) @maydiscard
macro void! sleep_ms(ulong ms) @maydiscard
macro void! sleep_ns(NanoDuration ns) @maydiscard
macro void yield()
std::thread::cpu @if(env::DARWIN)
fn uint native_cpu()
std::thread::cpu @if(env::LINUX)
fn uint native_cpu()
std::thread::cpu @if(env::WIN32)
fn uint native_cpu()
std::thread::os @if (!env::POSIX && !env::WIN32)
distinct NativeConditionVariable = int;
distinct NativeMutex = int;
distinct NativeOnceFlag = int;
distinct NativeThread = int;
std::thread::os @if(env::LINUX)
distinct Pthread_attr_t = ulong[7]; // 24 on 32bit
distinct Pthread_cond_t = ulong[6];
distinct Pthread_condattr_t = uint;
distinct Pthread_key_t = uint;
distinct Pthread_mutex_t = ulong[5]; // 24 on 32 bit
distinct Pthread_mutexattr_t = uint;
distinct Pthread_once_t = int;
distinct Pthread_rwlock_t = ulong[7]; // 32 on 3bit
distinct Pthread_rwlockattr_t = uint;
distinct Pthread_sched_param = uint;
std::thread::os @if(env::POSIX && !env::LINUX)
distinct Pthread_attr_t = ulong[8];
distinct Pthread_cond_t = ulong[6];
distinct Pthread_condattr_t = ulong[8];
distinct Pthread_key_t = ulong;
distinct Pthread_mutex_t = ulong[8];
distinct Pthread_mutexattr_t = ulong[2];
distinct Pthread_once_t = ulong[2];
distinct Pthread_rwlock_t = ulong[25];
distinct Pthread_rwlockattr_t = ulong[3];
distinct Pthread_sched_param = ulong;
std::thread::os @if(env::POSIX)
struct NativeMutex
fn void! NativeConditionVariable.broadcast(&cond)
fn void! NativeConditionVariable.destroy(&cond)
fn void! NativeConditionVariable.init(&cond)
fn void! NativeConditionVariable.signal(&cond)
fn void! NativeConditionVariable.wait(&cond, NativeMutex* mtx)
fn void! NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, ulong ms)
fn void! NativeMutex.destroy(&self)
fn void! NativeMutex.init(&self, MutexType type)
fn bool NativeMutex.is_initialized(&self)
fn void! NativeMutex.lock(&self)
fn void! NativeMutex.lock_timeout(&self, ulong ms)
fn bool NativeMutex.try_lock(&self)
fn void! NativeMutex.unlock(&self)
fn void NativeOnceFlag.call_once(&flag, OnceFn func)
fn void! NativeThread.create(&thread, ThreadFn thread_fn, void* arg)
fn void! NativeThread.detach(thread)
fn bool NativeThread.equals(thread, NativeThread other)
fn int! NativeThread.join(thread)
fn void! native_sleep_nano(NanoDuration nano)
fn NativeThread native_thread_current()
fn void native_thread_exit(int result)
fn void native_thread_yield()
std::thread::os @if(env::WIN32)
distinct NativeThread = inline Win32_HANDLE;
struct NativeConditionVariable
struct NativeMutex
struct NativeOnceFlag
fn void! NativeConditionVariable.broadcast(&cond)
fn void! NativeConditionVariable.destroy(&cond) @maydiscard
fn void! NativeConditionVariable.init(&cond)
fn void! NativeConditionVariable.signal(&cond)
fn void! NativeConditionVariable.wait(&cond, NativeMutex* mtx) @inline
fn void! NativeConditionVariable.wait_timeout(&cond, NativeMutex* mtx, uint time) @inline
fn void! NativeMutex.destroy(&mtx)
fn void! NativeMutex.init(&mtx, MutexType type)
fn void! NativeMutex.lock(&mtx)
fn void! NativeMutex.lock_timeout(&mtx, usz ms)
fn bool NativeMutex.try_lock(&mtx)
fn void! NativeMutex.unlock(&mtx)
fn void NativeOnceFlag.call_once(&flag, OnceFn func)
fn void! NativeThread.create(&thread, ThreadFn func, void* args)
fn void! NativeThread.detach(thread) @inline
fn bool NativeThread.equals(thread, NativeThread other)
fn int! NativeThread.join(thread)
fn void! native_sleep_nano(NanoDuration ns)
fn NativeThread native_thread_current()
fn void native_thread_exit(int result) @inline
fn void native_thread_yield()
std::thread::pool(<SIZE>)
struct QueueItem
struct ThreadPool
fn void! ThreadPool.destroy(&self)
fn void! ThreadPool.init(&self)
fn void! ThreadPool.push(&self, ThreadFn func, void* arg)
fn void! ThreadPool.stop_and_destroy(&self)
std::time
distinct Clock = ulong;
distinct Duration = long;
distinct NanoDuration (Printable) = long;
distinct Time = long;
enum Month : char
enum Weekday : char
struct DateTime
struct TzDateTime
fn long Duration.to_ms(td)
fn NanoDuration Duration.to_nano(td)
fn Duration NanoDuration.to_duration(nd)
fn usz! NanoDuration.to_format(&self, Formatter* formatter) @dynamic
fn long NanoDuration.to_ms(nd)
fn double NanoDuration.to_sec(nd)
fn Time Time.add_days(time, long days)
fn Time Time.add_duration(time, Duration duration)
fn Time Time.add_hours(time, long hours)
fn Time Time.add_minutes(time, long minutes)
fn Time Time.add_seconds(time, long seconds)
fn Time Time.add_weeks(time, long weeks)
fn double Time.diff_days(time, Time other)
fn double Time.diff_hour(time, Time other)
fn double Time.diff_min(time, Time other)
fn double Time.diff_sec(time, Time other)
fn Duration Time.diff_us(time, Time other)
fn double Time.diff_weeks(time, Time other)
fn double Time.to_seconds(time)
fn Duration from_float(double s) @inline
fn Duration hour(long l) @inline
fn Duration min(long l) @inline
fn Duration ms(long l) @inline
fn Time now()
fn Duration sec(long l) @inline
std::time::clock
fn NanoDuration Clock.mark(&self)
fn NanoDuration Clock.to_now(self)
fn Clock now()
std::time::datetime @if(env::LIBC)
fn DateTime DateTime.add_days(&self, int days)
fn DateTime DateTime.add_hours(&self, int hours)
fn DateTime DateTime.add_minutes(&self, int minutes)
fn DateTime DateTime.add_months(&self, int months)
fn DateTime DateTime.add_seconds(&self, int seconds)
fn DateTime DateTime.add_weeks(&self, int weeks)
fn DateTime DateTime.add_years(&self, int years)
fn bool DateTime.after(&self, DateTime compare) @inline
fn bool DateTime.before(&self, DateTime compare) @inline
fn int DateTime.compare_to(&self, DateTime compare)
fn double DateTime.diff_sec(self, DateTime from)
fn Duration DateTime.diff_us(self, DateTime from)
fn int DateTime.diff_years(&self, DateTime from)
fn void DateTime.set_date(&self, int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0)
fn void DateTime.set_time(&self, Time time)
fn TzDateTime DateTime.to_local(&self)
fn Time DateTime.to_time(&self) @inline
fn DateTime from_date(int year, Month month = JANUARY, int day = 1, int hour = 0, int min = 0, int sec = 0, int us = 0)
fn DateTime from_time(Time time)
fn DateTime now()
std::time::os @if(env::DARWIN)
struct Darwin_mach_timebase_info
fn Clock native_clock()
std::time::os @if(env::POSIX)
fn Clock native_clock() @if(!env::DARWIN)
fn Time native_timestamp()
std::time::os @if(env::WIN32)
fn Clock native_clock()
fn Time native_timestamp()
title: Conversions and Promotions description: Conversions and Promotions sidebar:
order: 210
Conversion Rules For C3
C3 differs in some crucial respects when it comes to number conversions and promotions. These are the rules for C3:
float
toint
conversions require a cast.int
tofloat
conversions do not require a cast.bool
tofloat
converts to0.0
or1.0
- Widening
float
conversions are only conditionally allowed*. - Narrowing conversions require a cast*.
- Widening
int
conversions are only conditionally allowed*. - Signed <-> unsigned conversions of the same type do not require a cast.
- In conditionals
float
tobool
do not require a cast, any non zerofloat
value considered true. - Implicit conversion to
bool
only occurs in conditionals or when the value is enclosed in()
e.g.bool x = (1.0)
orif (1.0) { ... }
C3 uses two’s complement arithmetic for all integer math.
:::note These abbreviations are used in the text below: - “lhs” meaning “left hand side”. - “rhs” meaning “right hand side”. :::
Target type
The left hand side of an assignment, or the parameter type in a call is known as the target type the target type is used for implicit widening and inferring struct initialization.
Common arithmetic promotion
Like C, C3 uses implicit arithmetic promotion of integer and floating point variables before arithmetic operations:
- For any floating point type with a bitwidth smaller than 32 bits, widen to
float
. E.g.f16 -> float
- For an integer type smaller than the minimum arithmetic width promote the value to a same signed integer of the minimum arithmetic width (this usually corresponds to a c int/uint). E.g.
ushort -> uint
Implicit narrowing
An expression with an integer type, may implicitly narrow to smaller integer type, and similarly a float type may implicitly narrow to less wide floating point type is determined from the following algorithm.
- Shifts and assign look at the lhs expression.
++
,--
,~
,-
,!!
,!
- check the inner type.+
,-
,*
,/
,%
,^
,|
,&
,??
,?:
- check both lhs and rhs.- Narrowing
int
/float
cast, assume the type is the narrowed type. - Widening
int
/float
cast, look at the inner expression, ignoring the cast. - In the case of any other cast, assume it is opaque and the type is that of the cast.
- In the case of an integer literal, instead of looking at the type, check that the integer would fit the type to narrow to.
- For
.len
access, allow narrowing to C int width. - For all other expressions, check against the size of the type.
As rough guide: if all the sub expressions originally are small enough it’s ok to implicitly convert the result.
Examples:
float16 h = 12.0;
float f = 13.0;
double d = 22.0;
char x = 1;
short y = -3;
int z = 0xFFFFF;
ulong w = -0xFFFFFFF;
x = x + x; // => calculated as x = (char)((int)x + (int)x);
x = y + x; // => Error, narrowing not allowed as y > char
h = x * h; // => calculated as h = (float16)((float)x * (float)h);
h = f + x; // => Error, narrowing not allowed since f > f16
Implicit widening
Unlike C, implicit widening will only happen on “simple expressions”: if the expression is a primary expression, or a unary operation on a primary expression.
For assignment, special rules hold. For an assignment to a binary expression, if its two subexpressions are “simple expressions” and the binary expression is +
, -
, /
, *
, allow an implicit promotion of the two sub expressions.
int a = ...
short b = ...
char c = ...
long d = a; // Valid - simple expression.
int e = (int)(d + (a + b)); // Error
int f = (int)(d + ~b); // Valid
long g = a + b; // Valid
As a rule of thumb, if there are more than one possible conversion an explicit cast is needed.
Example:
long h = a + (b + c);
// Possible intention 1
long h = (long)(a + (b + c));
// Possible intention 2
long h = (long)a + (long)(b + c);
// Possible intention 3
long h = (long)a + ((long)b + (long)c);
Maximum type
The maximum type is a concept used when unifying two or more types. The algorithm follows:
- First perform implicit promotion.
- If both types are the same, the maximum type is this type.
- If one type is a floating point type, and the other is an integer type,
the maximum type is the floating point type. E.g.
int + float -> float
. - If both types are floating point types, the maximum type is the widest floating point type. E.g.
float + double -> double
. - If both types are integer types with the same signedness, the
maximum type is the widest integer type of the two. E.g.
uint + ulong -> ulong
. - If both types are integer types with different signedness, the
maximum type is a signed integer with the same bit width as the maximum integer type.
ulong + int -> long
- If at least one side is a struct or a pointer to a struct with an
inline
directive on a member, check recursively check if the type of the inline member can be used to find a maximum type (see below under sub struct conversions) - All other cases are errors.
Substruct conversions
Substructs may be used in place of its parent structs in many cases. The rule is as follows:
- A substruct pointer may implicitly convert to a parent struct.
- A substruct value may be implicitly assigned to a variable with the parent struct type, This will truncate the value, copying only the parent part of the substruct. However, a substruct value cannot be assigned its parent struct.
- Substruct slices and arrays can not be cast (implicitly or explicitly) to an array of the parent struct type.
Pointer conversions
Pointer conversion between types usually need explicit casts.
The exception is void *
which any type may implicitly convert to or from.
Conversion rules from and to arrays are detailed under arrays
Vector conversions
Conversion between underlying vector types need explicit conversions. They work
as regular conversions with one notable exception: converting a true
boolean
vector value into an int will yield a value with all bits set. So bool[<2>] { true, false }
converted to for example char[<2>]
will yield { 255, 0 }
.
Vectors can also be cast to the corresponding array type, for example: char[<2>]
<=> char[2]
.
Binary conversions
1. Multiplication, division, remainder, subtraction / addition with both operands being numbers
These operations are only valid for integer and float types.
- Resolve the operands.
- Find the maximum type of the two operands.
- Promote both operands to the resulting type if both are simple expressions
- The resulting type of the expression is the maximum type.
2. Addition with left side being a pointer
- Resolve the operands.
- If the rhs is not an integer, this is an error.
- If the rhs has a bit width that exceeds isz, this is an error.
- The result of the expression is the lhs type.
3. Subtraction with lhs pointer and rhs integer
- Resolve the operands.
- If the right hand type has a bit width that exceeds isz, this is an error.
- The result of the expression is the left hand type.
4. Subtraction with both sides pointers
- Resolve the operands.
- If the either side is a
void *
, it is cast to the other type. - If the types of the sides are different, this is an error.
- The result of the expression is isz.
- If this result exceeds the target width, this is an error.
6. Bit operations ^
&
|
These operations are only valid for integers and booleans.
- Resolve the operands.
- Find the maximum type of the two operands.
- Promote both operands to the maximum type if they are simple expressions.
- The result of the expression is the maximum type.
6. Shift operations <<
>>
These operations are only valid for integers.
- Resolve the operands.
- In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side.
- The result of the expression is the lhs type.
7. Assignment operations +=
-=
*=
*=
/=
%=
^=
|=
&=
- Resolve the lhs.
- Resolve the right operand as an assignment rhs.
- The result of the expression is the lhs type.
8. Assignment shift >>=
<<=
- Resolve both operands
- In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side.
- The result of the expression is the lhs type.
9. &&
and ||
- Resolve both operands.
- Insert bool cast of both operands.
- The type is bool.
10. <=
==
>=
!=
- Resolve the operands, left to right.
- Find the maximum type of the two operands.
- Promote both operands to the maximum type.
- The type is bool.
Unary conversions
1. Bit negate
- Resolve the inner operand.
- If the inner type is not an integer this is an error.
- The type is the inner type.
2. Boolean not
- Resolve the inner operand.
- The type is bool.
3. Negation
- Resolve the inner operand.
- If the type inner type is not a number this is an error.
- If the inner type is an unsigned integer, cast it to the same signed type.
- The type is the type of the result from (3)
4. &
and &&
- Resolve the inner operand.
- The type is a pointer to the type of the inner operand.
5. *
- Resolve the inner operand.
- If the operand is not a pointer, or is a
void *
pointer, this is an error. - The type is the pointee of the inner operand’s type.
Dereferencing 0 is implementation defined.
6. ++
and --
- Resolve the inner operand.
- If the type is not a number, this is an error.
- The type is the same as the inner operand.
Base expressions
1. Typed identifiers
- The type is that of the declaration.
- If the width of the type is less than that of the target type, widen to the target type.
- If the width of the type is greater than that of the target type, it is an error.
2. Constants and literals
- If the constant is an integer, it is assumed to be the arithmetic promotion width and signed.
If the suffix
u
is added, it is assumed to be an unsigned number. If a suffixixx
oruxx
is given then it is considered a an integer of that type width and signedness. It cannot be implicitly narrowed. - If the constant is a floating point value, it is assumed to be a
double
unless suffixed withf
which is then assumed to be afloat
. If a bit width is given afterf
, it is instead a floating point type of that width.
title: Precedence description: Precedence sidebar:
order: 211
Precedence rules in C3 differs from C/C++. Here are all precedence levels in C3, listed from highest (1) to lowest (11):
()
,[]
,.
,!!
postfix!
,++
and--
@
, prefix-
,~
, prefix*
,&
, prefix++
and--
- infix
*
,/
,%
<<
,>>
^
,|
, infix&
+
, infix-
,+++
==
,!=
,>=
,<=
,>
,<
&&
,&&&
||
,|||
- ternary
?:
??
=
,*=
,/=
,%=
,+=
,-=
,<<=
,>>=
,&=
,^=
,|=
The main difference is that bitwise operations and shift has higher precedence than addition/subtraction and multiplication/division in C3. Bitwise operations also have higher precedence than the relational operators. Also, there is no difference in precedence between && || or between the bitwise operators.
Examples:
a + b >> c + d
(a + b) >> (c + d) // C (+ - are evaluated before >>)
a + (b >> c) + d // C3 (>> is evaluated before + -)
a & b == c
a & (b == c) // C (bitwise operators are evaluated after relational)
(a & b) == c // C3 (bitwise operators are evaluated before relational)
a > b == c < d
(a > b) == (c < d) // C (< > binds tighter than ==)
((a > b) == c) < d // C3 Error, requires parenthesis!
a | b ^ c & d
a | ((b ^ c) & d) // C (All bitwise operators have different precedence)
((a | b) ^ c) & d // C3 Error, requires parenthesis!
The change in precedence of the bitwise operators corrects a long standing issue in the C specification. The change in precedence for shift operations goes towards making the precedence less surprising.
Conflating the precedence of relational and equality operations, and all bitwise operations was motivated by simplification: few remember the exact internal differences in precedence between bitwise operators. Parenthesis are required for those conflated levels of precedence.
Left- to-right offers a very simple model to think about the internal order of operations, and encourages use of explicit ordering, as best practice in C is to use parentheses anyway.
title: Undefined Behaviour description: Undefined Behaviour sidebar:
order: 212
Like C, C3 uses undefined behaviour. In contrast, C3 will trap - that is, print an error trace and abort – on undefined behaviour in debug builds. This is similar to using C with a UB sanitizer. It is only during release builds that actual undefined behaviour occurs.
In C3, undefined behaviour means that the compiler is free to interpret undefined behaviour as if behaviour cannot occur.
In the example below:
uint x = foo();
uint z = 255 / x;
return x != 0;
The case of x == 0
would invoke undefined behaviour for 255/x
. For that reason,
the compiler may assume that x != 0
and compile it into the following code:
foo();
return true;
As a contrast, the safe build will compile code equivalent to the following.
uint x = foo();
if (x == 0) trap("Division by zero")
return true;
List of undefined behaviours
The following operations cause undefined behaviour in release builds of C3:
operation | will trap in safe builds |
---|---|
int / 0 | Yes |
int % 0 | Yes |
reading explicitly uninitialized memory | Possible* |
array index out of bounds | Yes |
dereference null |
Yes |
dereferencing memory not allocated | Possible* |
dereferencing memory outside of its lifetime | Possible* |
casting pointer to the incorrect array | Possible* |
violating pre or post conditions | Yes |
violating asserts | Yes |
reaching unreachable() code |
Yes |
* “Possible” indicates trapping is implementation dependent.
List of implementation dependent behaviours
Some behaviour is allowed to differ between implementations and platforms.
operation | will trap in safe builds | permitted behaviour |
---|---|---|
comparing pointers of different provenance | Optional | Any result |
subtracting pointers of different provenance | Optional | Any result |
shifting by more or equal to the bit width | Yes | Any result |
shifting by negative amount | Yes | Any result |
conversion floating point <-> integer type is out of range | Optional | Any result |
conversion between pointer types produces one with incorrect alignment | Optional | Any result / Error |
calling a function through a function pointer that does not match the function | Optional | Any result / Error |
attempt to modify a string literal | Optional | Partial modification / Error |
modifying a const variable |
Optional | Partial modification / Error |
List of undefined behaviour in C, which is defined in C3
Signed Integer Overflow
Signed integer is always wrapped using 2s complement.
Modifying the intermediate results of an expression
Behaves as if the intermediate result was stored in a variable on the stack.
title: Builtins description: Builtins sidebar:
order: 226
The compiler offers builtin constants and functions. Some are only available on certain targets. All builtins use the $$
name prefix.
Builtin constants
These constants are generated by the compiler and can safely be used by the user.
$$BENCHMARK_NAMES
An array of names of the benchmark functions.
$$BENCHMARK_FNS
An array of addresses to the benchmark functions.
$$DATE
The current date.
$$FILE
The current file name.
$$FILEPATH
The current file with path.
$$FUNC
The current function name, will return “
$$FUNCTION
The current function as an expression.
$$LINE
The current line as an integer.
$$LINE_RAW
Usually the same as $$LINE, but in case of a macro inclusion it returns the line in the macro rather than the line where the macro was included.
$$MODULE
The current module name.
$$TIME
The current time.
Compiler builtin functions
The $$
namespace defines compiler builtin functions.
These special functions are not guaranteed to exist on
all platforms, and are ways to wrap compiler implemented, optimized implementations
of some particular functionality. They are mainly intended for standard
library internal use. The standard library have macros
that wrap these builtins, so they should normally not be used on their own.
$$trap
Emits a trap instruction.
$$unreachable
Inserts an “unreachable” annotation.
$$stacktrace
Returns the current “callstack” reference if available. OS and compiler dependent.
$$volatile_store
Takes a variable and a value and stores the value as a volatile store.
$$volatile_load
Takes a variable and returns the value using a volatile load.
$$memcpy
Builtin memcpy instruction.
$$memset
Builtin memset instruction.
$$prefetch
Prefetch a memory location.
$$sysclock
Access to the cycle counter register (or similar low latency clock) on supported architectures (e.g. RDTSC on x86), otherwise $$sysclock will yield 0.
$$syscall
Makes a syscall according to the platform convention on platforms where it is supported.
Math functions
Functions $$ceil
, $$trunc
, $$sin
, $$cos
, $$log
, $$log2
, $$log10
, $$rint
, $$round
$$sqrt
, $$roundeven
, $$floor
, $$sqrt
, $$pow
, $$exp
, $$fma
and $$fabs
, $$copysign
,
$$round
, $$nearbyint
.
Can be applied to float vectors or numbers. Returns the same type.
Functions $$min
, $$abs
and $$max
can be applied to any integer or float number or vector.
Function $pow_int takes a float or floating vector + an integer and returns the same type as the first parameter.
Saturated addition, subtraction and left shift for integers and integer vectors:
$$sat_add
, $$sat_shl
, $$sat_sub
.
Bit functions
$$fshl and $$fshr
Funnel shift left and right, takes either two integers or two integer vectors.
$$ctz, $$clz, $$bitreverse, $$bswap, $$popcount
Bit functions work on an integer or an integer vector.
Vector functions
$$reduce_add
, $$reduce_mul
, $$reduce_and
, $$reduce_or
, $$reduce_xor
work on integer vectors.
$$reduce_fadd
, $$reduce_fmul
works on float vectors.
$$reduce_max
, $$reduce_min
works on any vector.
$$reverse
reverses the values in any vector.
$$shufflevector
rearranges the values of two vectors using a fixed mask into
a resulting vector.
title: Library Packaging description: Library Packaging sidebar:
order: 227
Note, the library system is in early alpha, everything below is subject to change
C3 allows convenient packaging of C3 source files optionally with statically or dynamically linked libraries. To use such a library, simply pass the path to the library directory and add the library you wish to link to. The compiler will resolve any dependencies to other libraries and only compile those that are in use.
How it works
A library may be used either packaged or unpacked. If unpacked, it is simply a directory with the .c3l
suffix, which contains all the necessary files, if packed, this is simply a compressed variant of
a directory with the same structure.
The specification
In the top of the library resides the manifest.json
file which has the following structure:
{
"provides" : "my_lib",
"execs" : [],
"targets" : {
"macos-x64" : {
"linkflags" : [],
"dependencies" : [],
"linked-libs" : ["my_lib_static", "Cocoa.framework", "c"]
},
"windows-x64" : {
"linkflags" : ["/stack:65536"],
"dependencies" : ["ms_my_extra"],
"linked-libs" : ["my_lib_static", "kernel32"],
"execs" : [],
}
}
}
In the example here, this library supports two targets: macos-x64 and windows-x64. If we tried to use it with any other target, the compiler would give an error.
We see that if we use the windows-x64 target it will also load the ms_my_extra library. And we also see that the linker would have a special argument on that platform.
Both targets expect my_lib_static
to be available for linking. If this library provides this
or dynamic library it will be in the target sub-directories, so it likely has the path
windows-x64/my_lib_static.lib
and macos-z64/libmy_lib_static.a
.
Source code
Aside from the manifest, C3 will read any C and C3 files in the same directory as manifest.json
as well as any files in the target subdirectory for the current target. For static libraries
typically a .c3i
file (that is, a C3 file without any implementations) is provided, similar to
how .h files are used in C.
Additional actions
"exec"
, which is available both at the top level and per-target, lists the scripts which will be
invoked when a library is used. This requires running the compiler at full trust level using the
--trust=full
option.
How to – automatically – export libraries
This is not implemented yet, docs will materialize once it is finished
title: Inline Assembly description: Inline Assembly sidebar:
order: 232
C3 provides two ways to insert inline assembly: asm strings and asm blocks.
Asm strings
This form takes a single compile time string and passes it directly to the underlying backend without any changes.
int x = 0;
asm("nop");
int y = x;
Asm block
Asm blocks uses a common grammar for all types of processors. It assumes that all assembly statements can be reduced to the format:
instruction (arg (',' arg)*)?;
Where an arg is:
- An identifier, e.g.
FOO
,x
. - A numeric constant
1
0xFF
etc. - A register name (always lower case with a ‘$’ prefix) e.g.
$eax
$r7
. - The address of a variable e.g.
&x
. - An indirect address:
[addr]
or[addr + index * <const> + offset]
. - Any expression inside of “()” (will be evaluated before entering the
asm
block).
An example:
int aa = 3;
int g;
int* gp = &g;
int* xa = &a;
usz asf = 1;
asm
{
movl x, 4; // Move 4 into the variable x
movl [gp], x; // Move the value of x into the address in gp
movl x, 1; // Move 1 into x
movl [xa + asf * 4 + 4], x; // Move x into the address at xa[asf + 1]
movl $eax, (23 + x); // Move 23 + x into EAX
movl x, $eax; // Move EAX into x
movq [&z], 33; // Move 33 into the memory address of z
}
The asm block will infer register clobbers and in/out parameters.
\Please note that the current state of inline asm is a work in progress, only a subset of x86, aarch64 and riscv instructions are available, other platforms have no support at all. It is likely that the grammar will be extended as more architectures are supported. More instructions can be added as they are needed, so please file issues when you encounter missing instructions you need.*
title: Build Commands description: Build Commands sidebar:
order: 240
import { FileTree } from ‘@astrojs/starlight/components’;
Building a project is done by invoking the C3 compiler with the build
or run
command inside of the project structure. The compiler will search upwards in the file hierarchy until a project.json
file is found.
You can also customise the project build config.
Compile Individual Files
By default the compiler is compiling stand-alone files to output an executable binary.
c3c compile <file1> <file2> <file3>
Run
When starting out, with C3 it’s natural to use compile-run
to try things out. For larger projects, the built-in build system is recommended instead.
The compile-run
command works same as compilation, but also immediately runs the resulting executable.
c3c compile-run <file1> <file2> <file3>
Common additional parameters
Additional parameters:
- --lib <path>
add a library to search.
- --output <path>
override the output directory.
- --path <path>
execute as if standing at <path>
Init a new project
c3c init <project_name> [optional path]
Create a new project structure in the current directory.
Use the --template
to select a template. The following are built in:
exe
- the default template, produces an executable.static-lib
- template for producing a static library.dynamic-lib
- template for producing a dynamic library.
It is also possible to give the path to a custom template.
Additional parameters:
- --template <path>
indicate an alternative template to use.
For example c3c init hello_world
creates the following structure:
Check the project configuration docs to learn more about configuring your project.
Test
c3c test
Will run any tests in the project in the "sources"
directory defined in your project.json
for example:
...
"sources": [ "src/**" ],
...
Tests are defined with a @test
attribute, for example:
fn void! test_fn() @test
{
assert(true == true, "true is definitely true");
}
Build
c3c build [target]
Build the project in the current path. It doesn’t matter where in the project structure you are.
The built-in templates define two targets: debug
(which is the default) and release
.
Clean
c3c clean
Build and Run
c3c run [target]
Build the target (if needed) and run the executable.
Clean and Run
c3c clean-run [target]
Clean, build and run the target.
Dist
c3c dist [target]
Not properly added yet
Clean, build and package the target for distribution. Will also run the target if it is a executable.
Docs
c3c docs [target]
Not added yet
Rebuilds the documentation.
Bench
c3c bench [target]
Runs benchmarks on a target.
title: Project Configuration description: Project Configuration sidebar:
order: 241
Customizing A Project
This is a description of the configuration options in project.json
:
{
// Language version of C3.
"langrev": "1",
// Warnings used for all targets.
"warnings": [ "no-unused" ],
// Directories where C3 library files may be found.
"dependency-search-paths": [ "lib" ],
// Libraries to use for all targets.
"dependencies": [ ],
// Authors, optionally with email.
"authors": [ "John Doe <john.doe@example.com>" ],
// Version using semantic versioning.
"version": "0.1.0",
// Sources compiled for all targets.
"sources": [ "src/**" ],
// C sources if the project also compiles C sources
// relative to the project file.
// "c-sources": [ "csource/**" ],
// Include directories for C sources relative to the project file.
// "c-include-dirs: [ "csource/include" ],
// Output location, relative to project file.
"output": "../build",
// Architecture and OS target.
// You can use 'c3c --list-targets' to list all valid targets,
// "target": "windows-x64",
// Current Target options:
// android-aarch64
// elf-aarch64 elf-riscv32 elf-riscv64 elf-x86 elf-x64 elf-xtensa
// mcu-x86 mingw-x64 netbsd-x86 netbsd-x64 openbsd-x86 openbsd-x64
// freebsd-x86 freebsd-x64 ios-aarch64
// linux-aarch64 linux-riscv32 linux-riscv64 linux-x86 linux-x64
// macos-aarch64 macos-x64
// wasm32 wasm64
// windows-aarch64 windows-x64
"targets": {
"linux-x64": {
// Executable or library.
"type": "executable",
// Additional libraries, sources
// and overrides of global settings here.
},
},
// Global settings.
// C compiler if the project also compiles C sources
// defaults to 'cc'.
"cc": "cc",
// CPU name, used for optimizations in the LLVM backend.
"cpu": "generic",
// Debug information, may be "none", "full" and "line-tables".
"debug-info": "full",
// FP math behaviour: "strict", "relaxed", "fast".
"fp-math": "strict",
// Link libc other default libraries.
"link-libc": true,
// Memory environment: "normal", "small", "tiny", "none".
"memory-env": "normal",
// Optimization: "O0", "O1", "O2", "O3", "O4", "O5", "Os", "Oz".
"opt": "O0",
// Code optimization level: "none", "less", "more", "max".
"optlevel": "none",
// Code size optimization: "none", "small", "tiny".
"optsize": "none",
// Relocation model: "none", "pic", "PIC", "pie", "PIE".
"reloc": "none",
// Trap on signed and unsigned integer wrapping for testing.
"trap-on-wrap": false,
// Turn safety (contracts, runtime bounds checking, null pointer checks etc).
"safe": true,
// Compile all modules together, enables more inlining.
"single-module": true,
// Use / don't use soft float, value is otherwise target default.
"soft-float": false,
// Strip unused code and globals from the output.
"strip-unused": true,
// The size of the symtab, which limits the amount
// of symbols that can be used. Should usually not be changed.
"symtab": 1048576,
// Use the system linker.
"linker": "cc",
// Include the standard library.
"use-stdlib": true,
// Set general level of x64 cpu: "baseline", "ssse3", "sse4", "avx1", "avx2-v1", "avx2-v2", "avx512", "native".
"x86cpu": "native",
// Set max type of vector use: "none", "mmx", "sse", "avx", "avx512", "native".
"x86vec": "sse",
}
By default, an executable is assumed, but changing the type to "static-lib"
or "dynamic-lib"
creates static library and dynamic library targets respectively.
This part will be updated, stay tuned
Compilation options
The project file contains common settings at the top level, that can be overridden by each
target, by simply assigning that particular key. So if the top level defines target
to be macos-x64
and the actual target defines it to be windows-x64
, then the windows-x64
will be used for compilation.
Similarly, compiler command line parameters can be used in turn to override the target setting.
targets
The list of targets that can be built.
dependencies
List of C3 libraries (“.c3l”) to use when compiling the target.
sources
List of source files to compile and for tests which will run.
cc
C compiler to use for compiling C sources (if C sources are compiled together with C3 files).
c-sources
List of C sources to compile, using the default C compiler.
linker-search-paths
This adds paths for the linker to search, when linking normal C libraries.
linked-libraries
This is a list of C libraries to link to. The names need to follow the normal
naming standard for how libraries are provided to the system linker,
so for example on Linux, libraries have names like libfoo.a
but when
presented to the linker the name is foo
. As an example "linked-libraries": ["curl"]
would on Linux look for the library libcurl.a
and libcurl.so
in the
paths given by “linker-search-paths”.
version
Not handled yet
Version for library, will also be provided as a compile time constant.
authors
Not handled yet
List of authors to add for library compilation.
langrev
Not handled yet
The language revision to use.
features
This is a list of upper-case constants that can be tested for
in the source code using $feature(NAME_OF_FEATURE)
.
warnings
Not completely supported yet
List of warnings to enable during compilation.
opt
Optimization setting: O0, O1, O2, O3, O4, O5, Os, Oz.
Target options
type
This mandatory option should be one of “executable”, “dynamic-lib” and “static-lib”.
More types will be added
Using environment variables
Not supported yet
In addition to constants any values starting with “$” will be assumed to be environment variables.
For example “$HOME” would on unix systems return the home directory. For strings that start with $ but should not be interpreted as an environment variable. For example, the string "\$HOME"
would be interpreted as the plain string “$HOME”
title: Frequently Asked Questions description: Frequently asked questions about C3 sidebar:
order: 700
Standard library
Q: What are the most fundamental modules in the standard library?
A: By default C3 will implicitly import anything in std::core
into
your files. It contains string functions, allocators and conveniences for
doing type introspection. The latter is in particular useful when writing
contracts for macros:
std::core::array
functions for working with arrays.std::core::builtin
contains functions that are to be used without a module prefix,unreachable()
,bitcast()
,@catch()
and@ok()
are especially important.std::core::cinterop
contains types which will match the C types on the platform.std::core::dstring
Has the dynamic string type.std::core::mem
containsmalloc
etc, as well as functions for atomic and volatile load / store.std::core::string
has all string functionality, including conversions, splitting and searching strings.
Aside from the std::core
module, std::collections
is important as it
holds various containers. Of those the generic List
type in std::collections::list
and the HashMap
in std::collections::map
are very frequently used.
IO is a must, and std::io
contains std::io::file
for working with files,
std::io::path
for working with paths. std::io
itself contains
functionality to writing to streams in various ways. Useful streams can
be found in the stream
sub folder.
Also of interest could be std::net
for sockets. std::threads
for
platform independent threads, std::time
for dates and timers, libc
for
invoking libc functions. std::os
for working with OS specific code and
std::math
for math functions and vector methods.
Q: How do strings work?
A: C3 defines a native string type String
, which is a distinct char[]
. Because
char[]
is essentially a pointer + length, some care has to be taken to
ensure that the pointer is properly managed.
For dynamic strings, or as a string builder, use DString
. To get a String from
a DString you can either get a view using str_view()
or make a copy using copy_str()
.
In the former case, the String may become invalid if DString is then mutated.
ZString
is a distinct zero terminated char*
. It is used to model zero-terminated
strings like in C. It is mostly useful interfacing with C.
WString
is a Char16*
, useful on those platforms, like Win32, where this
is the common unicode format. Like ZString, it is mostly useful when interfacing
with C.
Language features
Q: How do I use slices?
A: Slices are typically preferred in any situation where one in C would pass a pointer + length. It is a struct containing a pointer + a length.
Given an array, pointer or another slice you use either [start..end]
or [start:len]
to create it:
int[100] a;
int[] b = a[3..6]; // Or a[3:4]
b[0] = 1; // Same as a[3] = 1
You can also just pass a pointer to an array:
b = &a; // Same as b = a[0..99];
The start and/or end may be omitted:
a[..6]; // a[0..6]
a[1..]; // a[1..99]
a[..]; // a[0..99];
It is possible to use ranges to assign:
a[1..2] = 5; // Assign 5 to a[1] and a[2]
a[1..3] = a[11..13]; // Copy 11-13 to 1-3
It is important to remember that the lifetime of a slice is the same as the lifetime of its underlying pointer:
fn int[] buggy_code()
{
int[3] a;
int[] b = a[0..1];
return b; // returning a pointer to a!
}
Q: How do I pass varargs to another function that takes varargs?
A: Use the splat operator, ...
fn void test(String format, args...)
{
io::printfn(format, ...args);
}
fn void main()
{
test("Format: %s %d", "Foo", 123);
}
Q: What are vectors?
A: Vectors are similar to arrays, but declared with [< >]
rather than [ ]
, the element type may also only
be of integer, float, bool or pointer types. Vectors are backed by SIMD types on supported platforms. Arithmetics
available on the element type is available on the vector and is done element wise:
int[<2>] pos = { 1, 3 };
int[<2>] speed = { 5, 7 };
pos += speed; // pos is now { 6, 10 }
Swizzling is also supported:
int[<3>] test = pos.yxx; // test is now { 10, 6, 6 }
Any scalar value will be expanded to the vector size:
// Same as speed = speed * { 2, 2 }
speed = speed * 2;
Memory management
Q: How do I work with memory?
A: There is malloc
, calloc
and free
just like in C. The main difference is that these will invoke whatever
the current heap allocator is, which does not need to be the allocator provided by libc. You can get the current heap
allocator using allocator::heap()
and do allocations directly. There is also a temporary allocator.
Convenience functions are available for allocating particular types: mem::new(Type)
would allocate a single Type
on the heap and zero initialize it. mem::alloc(Type)
does the same but without zero initialization.
Alternatively, mem::new
can take a second initializer argument:
Foo* f1 = malloc(Foo.sizeof); // No initialization
Foo* f2 = calloc(Foo.sizeof); // Zero initialization
Foo* f3 = mem::new(Foo); // Zero initialization
Foo* f4 = mem::alloc(Foo); // No initialization
Foo* f5 = mem::new(Foo, { 4, 10.0, .a = 123 }); // Initialized to argument
For arrays mem::new_array
and mem::alloc_array
works in corresponding ways:
Foo* foos1 = malloc(Foo.sizeof * len); // No initialization
Foo* foos2 = calloc(Foo.sizeof * len); // Zero initialization
Foo[] foos3 = mem::new_array(Foo, len); // Zero initialization
Foo[] foos4 = mem::alloc_array(Foo, len); // No initialization
Regardless of how they are allocated, they can be freed using free()
Q: How does the temporary allocator work?
A: The temporary allocator is a kind of stack allocator. talloc
, tcalloc
and trealloc
correspond to
malloc
, calloc
and realloc
. There is no free
, as temporary allocations are free when pool of temporary
objects are released. You use the @pool()
macro to create a temporary allocation scope. When execution exits
this scope, the temporary objects are all freed:
@pool()
{
void* some_mem = talloc(128);
foo(some_mem);
};
// Temporary allocations are automatically freed here.
Similar to the heap allocator, there is also mem::temp_new
, mem::temp_alloc
, mem::temp_new_array
and mem::temp_alloc_array
,
which all work like their heap counterparts.
Q: How can I return a temporarily allocated object from inside a temporary allocation scope?
A: You need to pass in a copy of the temp allocator outside of @pool
and allocate explicitly
using that allocator. In addition, you need to pass this temp allocator to @pool
to make the
new temp allocator aware of the external temp allocator:
// Store the temp allocator
Allocator* temp = allocator::temp();
@pool(temp)
{
// Note, 'allocator::temp() != temp' here!
void* some_mem = talloc(128);
// Allocate this on the external temp allocator
Foo* foo = temp.new(Foo);
foo.z = foo(some_mem);
// Now "some_mem" will be released,
// but the memory pointed to by "foo" is still valid.
return foo;
};
Interfacing with C code
Q: How do I call a C function from C3?
A: Just copy the C function definition and prefix it with extern
(and don’t forget the fn
as well).
Imagine for example that you have the function double test(int a, void* b)
. To call it from C3 just declare
extern fn double test(CInt a, void* b)
in the C3 code.
Q: My C function / global has a name that doesn’t conform to the C3 name requirements, just extern fn
doesn’t
work.
A: In this case you need to give the function a C3-compatible name and then use the @extern
attribute to
indicate its actual external name. For example, the function int *ABC(void *x)
could be declared in the C3 code as
extern fn int* abc(void* x) @extern("ABC")
.
There are many examples of this in the std::os
modules.
Patterns
Q: When do I put functionality in method and when is it a free function?
A: In the C3 standard library, free functions are preferred unless the function is only acting on the particular
type. Some exceptions exist, but prefer things like io::fprintf(file, "Hello %s", name)
over
file.fprintf("Hello %s", name)
. The former also has the advantage that it’s easier to extend to work with many
types.
Q: Are there any naming conventions in the standard library what one should know about?
A: Yes. A function or method with new
in the name will in general do one or more allocations and can take an
optional allocator. A function or method with temp
in the name will usually allocate using the temp allocator.
The method free
will free all memory associated with a type. destroy
is similar to free
but also indicates
that other resources (such as file handles) are released. In some cases close
is used instead of destroy
.
Function and variable names use snake_case
(all lower case with _
separating words).
Q: How do I create overloaded methods?
A: This can be achieved with macro methods.
Imagine you have two methods:
fn void Obj.func1(&self, String... args) @private {} // varargs variant
fn void Obj.func2(&self, Foo* pf) @private {} // Foo pointer variant
We can now create a macro method on Obj
which compiles to different calls depending on arguments:
// The macro must be vararg, since the functions take different amount of arguments
macro void Obj.func(&self, ...)
{
// Does it have a single argument of type 'Foo*'?
$if $vacount == 1 && @typeis($vaarg(0), Foo*):
// If so, dispatch to func2
return self.func2($vaarg(0));
$else
// Otherwise, dispatch all varargs to func1
return self.func1($vasplat());
$endif
}
The above would make it possible to use both obj.func("Abc", "Def")
and obj.func(&my_foo)
.
Platform support
Q: How do I use WASM?
A: Currently WASM support is really incomplete.
You can try this:
compile --reloc=none --target wasm32 -g0 --link-libc=no --no-entry mywasm.c3
Unless you are compiling with something that already runs initializers,
you will need to call the function runtime::wasm_initialize()
early in your
main or call it externally (for example from JS) with the name _initialize()
,
otherwise globals might not be set up properly.
This should yield an out.wasm
file, but there is no CI running on the WASM code
and no one is really using it yet, so the quality is low.
We do want WASM to be working really well, so if you’re interested in writing something in WASM - please reach out to the developer team and we’ll help you get things working.
Q: How do I conditionally compile based on compiler flags?
A: You can pass feature flags on the command line using -D SOME_FLAG
or using the features
key
in the project file.
You can then test for them using $feature(FLAG_NAME)
:
int my_var @if($feature(USE_MY_VAR));
fn int test()
{
$if $feature(USE_MY_VAR):
return my_var;
$else
return 0;
$endif
}
Syntax & Language design
Q: Why does C3 require that types start with upper case but functions with lower case?
A: C grammar is ambiguous. Usually compilers implement either the so-called lexer hack, but other methods exist as well, such as delayed parsing. It is also possible to make it unambiguous using infinite lookahead.
However, all of those methods makes it much harder for tools to search the source code accurately. By making the naming convention part of the grammar, C3 is straightforward to parse with a single token lookahead.
Q: Why are there no closures and only non-capturing lambdas?
A: With closures, life-time management of captured variables become important to track. This can become arbitrarily complex, and without RAII or any other memory management technique it is fairly difficult to make code safe. Non-capturing lambdas on the other hand are fairly safe.
Q: Why is it called C3 and not something better?
A: Naming a programming language isn’t easy. Most programming languages have pretty bad names, and while C3 isn’t the best, no real better alternative has come along.
Q: Why are there no static methods?
A: Static methods creates a tension between free functions in modules and functions namespaced by the type. Java for example, resolves this by not having free functions at all. C3 solves it by not having static methods (nor static variables). Consequently more functions becomes part of the module rather than the type.
Q: Why do macros with trailing bodies require ;
at the end?
@test()
{
// code
}; // <- Why is this needed?
A: All macro calls, including those with a trailing body, are expressions, so it would be ambiguous to let them terminate a statement without a much more complicated grammar. An example:
// How can the parser determine that the
// last `}` ends the expression? (And does it?)
int a = @test() {} + @test() {}
*b = 123;
// In comparison, the grammar for this is easy:
int a = @test() {} + @test() {};
*b = 123;
C3 strives for a simple grammar, and so the trade-off having to use ;
was a fairly
low prices to pay for this feature.
Q: Why do only macros have ref arguments?
A: Ref arguments break the general contract of a call: what looks like a pass-by-value
may suddenly be passed by reference. This makes code reading much harder, but is popular
in C++ because: (1) No need for ->
(2) It prevents passing of null pointers. Neither
of these are required in C3 (->
is not needed, and &
in the parameter contract will
prevent nulls).
This leaves the case where there is a benefit for the user to create an implicit &
on a call. These cases should be rare, and in C3, it’s not a problem creating a wrapper
macro in those cases.
Note that macros that violate the call contract, such as ones using ref arguments, need
to have the @
name prefix to indicate that it is indeed possibly violating
“value is passed by value” semantics.
title: All Features description: A list of all features of C3. sidebar:
order: 701
Here is a summary of all the features of C3 and changes from C
Symbols and literals
Changes relating to literals, identifiers etc.
Added
- 0o prefix for octal.
- 0b prefix for binary.
- Optional “_” as digit separator.
- Hexadecimal byte data, e.g
x"abcd"
. - Base64 byte data, e.g.
b64"QzM="
. - Type name restrictions (PascalCase).
- Variable and function name restrictions (must start with lower case letter).
- Constant name restrictions (no lower case).
- Character literals may be 2, 4, 8, 16 bytes long. (2cc, 4cc etc).
- Raw string literals between “`”.
\e
escape character.- Source code must be UTF-8.
- Assumes
\n
for new row\r
is stripped from source. - Bit-width integer and float suffixes:
u8
/i8
/u16
/i16
/…f32
/f64
/… - The
null
literal is a pointer value of 0. - The
true
andfalse
are boolean constants true and false.
Removed
- Trigraphs / digraphs.
- 0123-style octal.
z
,LL
andULL
suffixes.
Built-in types
Added
- Type declaration is left to right:
int[4]*[2] a;
instead ofint (*a[2])[4];
- Simd vector types using
[<>]
syntax, e.g.float[<4>]
, use[<*>]
for inferred length. - Slice type built in, using
[]
suffix, e.g.int[]
- Distinct types, similar to a typedef but forms a new type. (Example: the
String
type is a distinctchar[]
) - Built-in 128-bit integer on all platforms.
char
is an unsigned 8-bit integer.ichar
is its signed counterpart.- Well-defined bitwidth for integer types: ichar/char (8 bits), short/ushort (16 bits), int/uint (32 bits), long/ulong (64 bits), int128/uint128 (128 bits)
- Pointer-sized
iptr
anduptr
integers. isz
andusz
integers corresponding to thesize_t
bitwidth.- Optional types are formed using the
!
suffix. bool
is the boolean type.typeid
is a unique type identifier for a type, it can be used at runtime and compile time.any
contains atypeid
andvoid*
allowing it to act as a reference to any type of value.anyfault
holds anyfault
value (see below).
Changed
- Inferred array type uses
[*]
(e.g.int[*] x = { 1, 2 };
). - Flexible array member uses
[*]
.
Removed
- The spiral rule type declaration (see above).
- Complex types
- size_t, ptrdiff_t (see above).
- Array types do not decay.
Types
Added
bitstruct
a struct with a container type allowing precise control over bit-layout, replacing bitfields and enum masks.fault
an enum type with unique values which are used together with optional.- Vector types.
- Optional types.
enum
allows a set of unique constants to be associated with each enum value.- Compile time reflection and limited runtime reflection on types (see “Reflection”)
- All types have a
typeid
property uniquely referring to that particular type. - Distinct types, which are similar to aliases, but represent distinctly different types.
- Types may have methods. Methods can be added to any type, including built-in types.
- Subtyping: using
inline
on a struct member allows a struct to be implicitly converted to this member type and use corresponding methods. - Using
inline
on a distinct type allows it to be implicitly converted to its base type (but not vice versa). - Types may add operator overloading to support
foreach
and subscript operations. - Generic types through generic modules, using
(< ... >)
for the generic parameter list (e.g.List(<int>) list;
). - Interface types,
any
types which allows dynamic invocation of methods.
Changed
typedef
is replaced bydef
and has somewhat different syntax (e.g.def MyTypeAlias = int;
).- Function pointer syntax is prefix
fn
followed by a regular function declaration without the function name.
Removed
- Enums, structs and unions no longer have distinct namespaces.
- Enum, struct and union declarations should not have a trailing ‘;’
- Inline
typedef
is not allowed.def
can only be used at the top level. - Anonymous structs are not allowed.
- Type qualifiers are all removed, including
const
,restrict
,volatile
- Function pointers types cannot be used “raw”, but must always be used through a type alias.
Introspection
Compile time type methods: alignof
, associated
, elements
, extnameof
, inf
, inner
, kindof
, len
,
max
, membersof
, min
, nan
, names
, params
, returns
, sizeof
, typeid
, values
,
qnameof
, is_eq
, is_ordered
.
Runtime type methods: inner
, kind
, len
, names
, sizeof
.
Expressions
Added
- Expression block using
{| ... |}
. Somewhat similar to GCC statement expressions. - Array initializers may use ranges. (e.g.
int[256] x = { [0..128] = 1 }
) ?:
operator, returning the first value if it can be converted to a boolean true, otherwise the second value is returned.- Orelse
??
returning the first value if it is a result, the second if the first value was an optional value. - Rethrow
!
suffix operator with an implicitreturn
the value if it was an optional value. - Dynamic calls, allowing calls to be made on the
any
and interfaces dispatched using a dynamic mechanism. - Create a slice using a range subscript (e.g.
a[4..8]
to form a slice from element 4 to element 8). - Two range subscript methods:
[start..inclusive_end]
and[start:length]
. Start, end and length may be omitted for default values. - Indexing from end: slices, arrays and vectors may be indexed from the end using
^
.^1
represents the last element. This works for ranges as well. - Range assignment, assign a single value to an entire range e.g.
a[4..8] = 1;
. - Slice assignment, copy one range to the other range e.g.
a[4..8] = b[8..12];
. - Array, vector and slice comparison:
==
can be used to make an element-wise comparison of two containers. ?
suffix operator turns a fault into an optional value.!!
suffix panics if the value is an optional value.$defined(...)
returns true if the last expression is defined (sub-expressions must be valid).$and(...)
$or(...)
perform compile time logic, and may also be written as&&&
and|||
respectively.- It does not check any elements after the first false value found for
$and()
also written as&&&
. To check both conditions are false use:!false_condition &&& !false_condition
. - It does not check any values after the first true found for
$or()
also written as|||
. - Lambdas (anonymous functions) may be defined, they work just like functions and do not capture any state.
- Simple bitstructs (only containing booleans) may be manipulated using bit operations
& ^ | ~
and assignment. - Structs may implicitly convert to their
inline
member if they have one. - Pointers to arrays may implicitly convert to slices.
- Any pointer may implicitly convert to an
any
with type being the pointee. - Optional values will implicitly invoke “flatmap” on an expression it is a subexpression of.
- Swizzling for arrays and vectors.
Changed
- Compound literals use
Type { ... }
rather than(Type) { ... }
- Operator precedence of bit operations is higher than
+
and-
. - Well defined-evaluation order: left-to-right, assignment after expression evaluation.
sizeof
is$sizeof
and only works on expressions. UseType.sizeof
on types.alignof
is$alignof
for expressions. Types useType.alignof
.- Narrowing conversions are only allowed if all sub-expressions is as small or smaller than the type.
- Widening conversions are only allowed on simple expressions (i.e. most binary expressions and some unary may not be widened)
Removed
- The comma operator is removed.
Cast changes
Functions
Added
- Functions may be invoked using named arguments, the name is the dot-prefixed parameter name, e.g.
foo(name: a, len: 2)
. - Typed varargs are declared
Type... argument
, and will take 0 or more arguments of the given type. - It is possible to “splat” an array or slice into the location of a typed vararg using
...
:foo(a, b, ...list)
any
varargs are declaredargument...
, it can take 0 or more arguments of any type which are implicitly converted to theany
type.- The function declaration may have
@inline
or@noinline
as a default. - Using
@inline
or@noinline
on a function call expression will override the function default. - Type methods are functions defined in the form
fn void Foo.my_method(Foo* foo) { ... }
, they can be invoked using dot syntax. - Type methods may be attached to any type, even arrays and vectors.
- Error handling using optional return types.
Changed
- Function declarations use the
fn
prefix.
Removed
- Functions with C-style varargs may be called, and declared as external functions, but not used for C3 functions.
Attributes
C3 adds a long range of attributes in the form @name(...)
. It is possible to create custom
attribute groups using def
(e.g. def MyAttribute(usz align) = { @aligned(align) @weak };
) which
groups certain attributes. Empty attribute groups are permitted.
The complete list: @align
, @benchmark
, @bigendian
, @builtin
,
@callconv
, @deprecated
, @dynamic
, @export
,
@extern
, @if
, @inline
, @interface
,
@littleendian
, @local
, @maydiscard
, @naked
,
@nodiscard
, @noinit
, @noreturn
, @nostrip
,
@obfuscate
, @operator
, @overlap
, @priority
,
@private
, @public
, @pure
, @reflect
,
@section
, @test
, @used
, @unused
.
Declarations
Added
var
declaration for type inferred variables in macros. E.g.var a = some_value;
var
declaration for new type variables in macros. E.g.var $Type = int;
var
declaration for compile time mutable variables in function and macros. E.g.var $foo = 1;
const
declarations may be untyped. Such constants are not stored in the resulting binary.
Changed
tlocal
declares a variable to be thread local.static
top level declarations are replaced with@local
. (static
in functions is unchanged)
Removed
restrict
removed.atomic
should be replaced by atomic load/store operations.volatile
should be replaced by volatile load/store operations.
Statements
Added
- Match-style variant of the
switch
statement, allows eachcase
to hold an expression to test. - Switching over type with
typeid
. - Unpack
any
to the underlying type with anany
-switch. nextcase
to fallthrough to the next case.nextcase <expr>
to jump to the case with the expression value (this may be an expression evaluated at runtime).nextcase default
to jump to thedefault
clause.- Labelled
while
/do
/for
/foreach
to use withbreak
nextcase
andcontinue
. foreach
to iterate over arrays, vectors, slices and user-defined containers using operator overloading.foreach_r
to iterate in reverse.foreach
/foreach_r
may take the element by value or reference. The index may optionally be provided.$if
,$switch
,$for
,$foreach
statements executing at compile time.$echo
printing a message at compile time.$assert
compile time assert.defer
statement to execute statements at scope exit.defer catch
anddefer try
similar todefer
but executes only on optional exit or regular exit of scope respectively.do
statements may omitwhile
, behaving same aswhile (0)
if
may have a label. Labelledif
may be exited using labelled break.asm
blocks for inline assembly.- if-try statements allows you to run code where an expression is a result.
- if-catch statements runs code on fault. It can be used to implicitly unwrap variables.
- Exhaustive switching on enums.
Changed
- Switch cases will have implicit break, rather than implicit fallthrough.
assert
is an actual statement and may take a string or a format + arguments.static_assert
is$assert
and is a statement.
Removed
goto
removed, replaced by labelled break, continue and nextcase.
Compile time evaluation
Added
@if(cond)
to conditionally include a struct/union field, a user-defined type etc.- Compile time variables with
$
prefix e.g.$foo
. $if...$else...$endif
and$switch...$endswitch
inside of functions to conditionally include code.$for
and$foreach
to loop over compile time variables and data.$typeof
determines an expression type without evaluating it.- Type properties may be accessed at compile time.
$define
returns true if the variable, function or type exists.$error
emits an error if encountered.$embed
includes a file as binary data.$include
includes a file as text.$exec
includes the output of a program as code.$evaltype
takes a compile time string and turns it into a type.$eval
takes a string and turns it into an identifier.$extnameof
turns an identifier into its string external name.$nameof
turns an identifier into its local string name.$qnameof
turns an identifier into its local string name with the module prefixed.- Compile time constant values are always compile time folded for arithmetic operations and casts.
$$FUNCTION
returns the current function as an identifier.
Changed
#define
for constants is replaced by untyped constants, e.g.#define SOME_CONSTANT 1
becomesconst SOME_CONSTANT = 1;
.#define
for variable and function aliases is replaced bydef
, e.g.#define native_foo win32_foo
becomesdef native_foo = win32_foo;
- In-function
#if...#else..#endif
is replaced by$if
,#if...#elif...#endif
is replaced by$switch
. - For converting code into a string use
$stringify
. - Macros for date, line etc are replaced by
$$DATE
,$$FILE
,$$FILEPATH
,$$FUNC
,$$LINE
,$$MODULE
,$$TIME
.
Removed
- Top level
#if...#endif
does not have a counterpart. Use@if
instead. - No
#include
directives,$include
will include text but isn’t for the same use.
Macros
Added
macro
for defining macros.- “Function-like” macros have no prefix and has only regular parameters or type parameters.
- “At”-macros are prefixed with
@
and may also have compile time values, expression and ref parameters, and may have a trailing body. - Type parameters have the prefix
$
and conform to the type naming standard (“$TypeFoo”). - “ref” parameters are declared using with a
&
prefix operator. This is similar to C++ ref parameters. - Expression parameters are unevaluated expressions, this is similar to arguments to
#define
. - Compile time values have a
$
prefix and must contain compile time constant values. - Any macro that evaluates to a constant result can be used as if it was the resulting constant.
- Macros may be recursively evaluated.
- Macros are inlined at the location where they are invoked.
- Unless resulting in a single constant, macros implicitly create a runtime scope.
Removed
- No
#define
macros. - Macros cannot be incomplete statements.
Features provided by builtins
Some features are provided by builtins, and appears as normal functions and macros in the standard library but nonetheless provided unique functionality:
@likely(...)
/@unlikely(...)
on branches affects compilation optimization.@anycast(...)
casts anany
with an optional result.unreachable(...)
marks a path as unreachable with a panic in safe mode.unsupported(...)
similar to unreachable but for functionality not implemented.@expect(...)
expect a certain value with an optional probability for the optimizer.@prefetch(...)
prefect a pointer.swizzle(...)
swizzles a vector.@volatile_load(...)
and@volatile_store(...)
volatile load/store.@atomic_load(...)
and@atomic_store(...)
atomic load/store.compare_exchange(...)
atomic compare exchange.- Saturating add, sub, mul, shl on integers.
- Vector reduce operations: add, mul, and, or, xor, max, min.
Modules
- Modules are defined using
module <name>
. Where name is on the formfoo::bar::baz
- Modules can be split into an unlimited number of module sections, each starting with the same module name declaration.
- The
import
statement imports a given module. - Each module section has its own set of import statements.
- Importing a module gives access to the declarations that are
@public
. - Declarations are default
@public
, but a module section may set a different default (e.g.module my_module @private;
) @private
means the declaration is only visible in the module.@local
means only visible to the current module section.- Imports are recursive. For example,
import my_lib
will implicitly also importmy_lib::net
. - Multiple imports may be specified with the same
import
, e.g.import std::net, std::io;
. - Generic modules have a set of parameters after the module name
module arr(<Type, LEN>);
- Generic modules are not type checked until any of its types, functions or globals are instantiated.
Contracts
- Doc contracts (starting with
<*
) are parsed. - The first part, up until the first
@
directive on a new line, is ignored. - The
@param
directive for pointer arguments may define usage constraints[in]
[out]
and[inout]
. - Pointer argument constraints may add a
&
prefix to indicate that they may not benull
, e.g.[&inout]
. - Contracts may be attached to generic modules, functions and macros.
@require
directives are evaluated given the arguments provided. Failing them may be a compile time or runtime error.- The
@ensure
directive is evaluated at exit - if the return is a result and not an optional. return
can be used as a variable identifier inside of@ensure
, and holds the return value.@return!
optionally lists the errors used. This will be checked at compile time.@pure
says that no writing to globals is allowed inside and only@pure
functions may be called.
Benchmarking
- Benchmarks are indicated by
@benchmark
. - Marking a module section
@benchmark
makes all functions inside of it implicitly benchmarks. - Benchmarks are usually not compiled.
- Benchmarks are instead only run by the compiler on request.
Testing
- Tests are indicated by
@test
. - Marking a module section
@test
makes all functions inside of it implicitly tests. - Tests are usually not compiled.
- Tests are instead only run by the compiler on request.
Safe / fast
Compilation has two modes: “safe” and “fast”. Safe will insert checks for out-of-bounds access, null-pointer deref, shifting by negative numbers, division by zero, violation of contracts and asserts.
Fast will assume all of those checks can be assumed to always pass. This means that unexpected behaviour may result from violating those checks. It is recommended to develop in “safe” mode.
If debug symbols are available, C3 will produce a stack trace in safe mode where an error occurs.
title: Comparisons With Other Languages description: How C3 compares to other languages sidebar:
order: 701
An important question to answer is “How does C3 compare to other similar programming languages?”. Here is an extremely brief (and not yet complete) overview.
C
As C3 is an evolution of C, the languages are quite similar. C3 adds features, but also removes a few.
In C3 but not in C
- Module system
- Integrated build system
- Generics
- Semantic Macros
- Error handling
- Defer
- Value methods
- Associated enum data
- Distinct types and subtypes
- Optional contracts
- Built-in slices
- Foreach for iteration over arrays and types
- Dynamic calls and types
In C but not in C3
- Qualified types (
const
,volatile
etc) - Unsafe implicit conversions
C++
C++ is a complex object oriented “almost superset” of C. It tries to be everything to everyone, while squeezing this into a C syntax. The language is well known for its many pitfalls and quirky corners – as well as its long compile times.
C3 is in many ways different from C++ in the same way that C is different from C++, but the semantic macro system and the generics close the gap in terms of writing reusable generic code. The C3 module system and error handling is also very different from how C++ does things.
In C++ but not in C3
- Objects and classes
- RAII
- Exceptions
In C3 but not in C++
- Module system (yet)
- Integrated build system
- Semantic macros
- Error handling
- Defer
- Associated enum data
- Built-in slices
- Dynamic calls
Rust
Rust is a safe systems programming language. While not quite as complex as C++, it is still a feature rich programming language with semantic macros, traits and pattern matching to mention a few.
Error handling is handled using Result
and Optional
which is similar to
how C3 works.
C3 compares to Rust much like C, although the presence of built-in slices and strings reduces the places where C3 is unsafe. Rust provides arrays and strings, but they are not built in.
In Rust but not in C3
- RAII
- Memory safety
- Safe union types with functions
- Different syntax from C
- Pattern matching
- Async built in
In C3 but not in Rust
- Same ease of programming as C
- Optional contracts
- Familiar C syntax and behaviour
- Dynamic calls
Zig
Zig is a systems programming language with extensive compile time execution to enable polymorphic functions and parameterized types. It aims to be a C replacement.
Compared to C3, Zig tries to be a completely new language in terms of syntax and feel. C3 uses macros to a modest degree where it is more pervasive in Zig, and does not depart from C to the same degree. Like Rust, it features slices as a first class type. The standard library uses an explicit allocator to allow it to work with many different allocation strategies.
Zig is a very ambitious project, aiming to support as many types of platforms as possible.
In Zig but not in C3
- Pervasive compile time execution.
- Memory allocation failure is an error.
- Toolchain uses build files written in native Zig.
- Different syntax and behaviour compared to C.
- Structs define namespace.
- Async primitives built in.
- Arbitrary integer sizes.
In C3 but not in Zig
- Module system.
- Integrated build system.
- C ABI compatibility by default.
- Optional contracts.
- Familiar C syntax and behaviour.
- Dynamic interfaces.
- Built in benchmarks.
Jai
Jai is a programming language aimed at high performance game programming. It has an extensive compile time meta programming functionality, even to the point of being able to run programs at compile time. It also has compile-time polymorphism, a powerful macro system and uses an implicit context system to switch allocation schemes.
In Jai but not in C3
- Pervasive compile time execution.
- Jai’s compile time execution is the build system.
- Different syntax and behaviour compared to C.
- More powerful macro system than C3.
- Implicit constructors.
In C3 but not in Jai
- Module system.
- Integrated build system.
- Optional contracts.
- Familiar C syntax and behaviour.
- Fairly small language.
- Dynamic interfaces.
Odin
Odin is a language built for high performance but tries to remain a simple language to learn. Superficially the syntax shares much with Jai, and some of Jai’s features things – like an implicit context – also shows up in Odin. In contrast with both Jai and Zig, Odin uses only minimal compile time evaluation and instead only relies on parametric polymorphism to ensure reuse. It also contains conveniences, like maps and arrays built into the language. For error handling it relies on Go style tuple returns.
In Odin but not in C3
- Different syntax and behaviour compared to C.
- Ad hoc parametric polymorphism.
- Multiple return values.
- Error handling through multiple returns.
- A rich built in set of types.
In C3 but not in Odin
- Familiar C syntax and behaviour.
- Semantic macros.
- Value methods.
- Optional contracts.
- Built in error handling.
- Dynamic interfaces.
D
D is an incredibly extensive language, it covers anything C++ does and adds much more. D manages this with much fewer syntactic quirks than C++. It is a strong, feature-rich language.
In D but not in C3
- Objects and classes.
- RAII.
- Exceptions.
- Optional GC.
+ Many, many more features.
In C3 but not in D
- Fairly small language.
title: Changes From C description: Changes From C sidebar:
order: 702
Although C3 is trying to improve on C, this does not only mean addition of features, but also removal, or breaking changes:
No mandatory header files
There is a C3 interchange header format for declaring interfaces of libraries, but it is only used for special applications.
Removal of the old C macro system
The old C macro system is replaced by a new C3 macro system.
Import and modules
C3 uses module imports instead of header includes to link modules together.
Member access using .
even for pointers
The ->
operator is removed, access uses dot for both direct and pointer
access. Note that this is just single access: to access a pointer of a
pointer (e.g. int**
) an explicit dereference would be needed.
Different operator precedence
Notably bit operations have higher precedence than +/-, making code like this: a & b == c
evaluate like (a & b) == c
instead of C’s a & (b == c)
. See the page about precedence rules.
Removal of the const type qualifier
The const qualifier is only retained for actual constant variables. C3 uses a special type of post condition for functions to indicate that they do not alter in parameters.
<*
This function ensures that foo is not changed in the function.
@param [in] foo
@param [out] bar
*>
fn void test(Foo* foo, Bar* bar)
{
bar.y = foo.x;
// foo.x = foo.x + 1 - compile time error, can't write to 'in' param.
// int x = bar.y - compile time error, can't read from an 'out' param.
}
Rationale: const correctness requires littering const across the code base. Although const is useful, it provides weaker guarantees that it appears.
Fixed arrays do not decay and have copy semantics
C3 has three different array types. Variable arrays and slices decay to pointers, but fixed arrays are value objects and do not decay.
int[3] a = { 1, 2, 3 };
int[4]* b = &a; // No conversion
int* c = a; // ERROR
int* d = &a; // Valid implicit conversion
int* e = b; // Valid implicit conversion
int[3] f = a; // Copy by value!
Removal of multiple declaration syntax with initialization
Only a single declaration with initialization is allowed per statement in C3:
int i, j = 1; // ERROR
int a = 1; // Ok
int b, c; // Ok
In conditionals, a special form of multiple declarations are allowed but each must then provide its type:
for (int i = 0, int j = 1; i < 10; i++, j++) { ... }
Integer promotions rules and safe signed-unsigned comparisons
Promotion rules for integer types are different from C.
C3 allows implicit widening only
where there is only a single way to widen the expression. To explain the latter:
take the case of long x = int_val_1 + int_val_2
. In C this would widen the result of the addition:
long x = (long)(int_val_1 + int_val_2)
, but there is another possible
way to widen: long x = (long)int_val_1 + (long)int_val_2
. so in this case, the widening
is disallowed. However, long x = int_val_1
is unambiguous, so C3 permits it just like C (read more on the conversion page.
C3 also adds safe signed-unsigned comparisons: this means that comparing signed and unsigned values will always yield the correct result:
// The code below would print "Hello C3!" in C3 and "Hello C!" in C.
int i = -1;
uint j = 1;
if (i < j)
{
printf("Hello C3!\n");
}
else
{
printf("Hello C!\n");
}
Goto removed
goto
is removed and replaced with labelled break
and continue
together with the nextcase
statement that allows you to jump between cases in a switch
statement.
Rationale:
It is very difficult to make goto work well with defer and implicit
unwrapping of optional results. It is not just making the compiler
harder to write, but
the code is harder to understand as well. The replacements together with
defer
cover many if not all usages of goto
in regular code.
Implicit break in switches
Empty case
statements have implicit fall through in C3, otherwise the nextcase
statement is needed
nextcase
can also be used to jump to any other case statement in the switch.
switch (h)
{
case 1:
a = 1;
nextcase; // Fall through
case 2:
b = 123;
case 3:
a = 2;
nextcase 2; // Jump to case 2
default:
a = 111;
}
Locals variables are implicitly zeroed
In C global variables are implicitly zeroed out, but local variables aren’t.
In C3 local variables are zeroed out by default, but may be explicitly undefined
(using the @noinit
attribute) if you wish to match the C behaviour.
Rationale for this change
- In the “zero-is-initialization” paradigm, zeroing variables, in particular structs, is very common. By offering zero initialization by default this avoids a whole class of vulnerabilities.
- Another alternative that was considered for C3 was mandatory initialization, but this adds a lot of extra boilerplate.
- C3 also offers a way to opt out of zero-initialization, so the change comes at no performance loss.
Compound literal syntax changed
// C style:
call_foo((Foo) { 1, 2, 3 });
// C++ style (1):
call_foo(Foo(1, 2, 3));
// C++ style (2):
call_foo(Foo { 1, 2, 3 });
// C3:
call_foo(Foo { 1, 2, 3 } );
// C3 with inference:
call_foo({ 1, 2, 3 });
Bitfields replaced by bitstructs
Bitfields are replaced by bitstructs that have a well-defined encapsulating type, and an exact bit layout.
// C
struct Foo
{
int a : 3;
unsigned b : 4;
MyEnum c : 7;
};
struct Flags
{
bool has_hyperdrive : 1;
bool has_tractorbeam : 1;
bool has_plasmatorpedoes : 1;
}
// C3
bitstruct Foo : short
{
int a : 0..2;
uint b : 3..6;
MyEnum c : 7..13;
}
// Simple form, only allowed when all fields are bools.
struct Flags : char
{
bool has_hyperdrive;
bool has_tractorbeam;
bool has_plasmatorpedoes;
}
Evaluation order is well-defined
Evaluation order is left-to-right, and in assignment expressions, assignment happens after expression evaluation.
Signed overflow is well-defined
Signed integer overflow always wraps using 2s complement. It is never undefined behaviour.
Octal removed
The old 0777
octal syntax is removed and replaced by a 0o
prefix, e.g. 0o777
. Strings do not support octal sequences aside
from '\0'
.
title: Rejected Ideas description: Rejected Ideas sidebar:
order: 703
These are ideas that will not be implemented in C3 with rationale given.
Constructors and destructors
A fundamental concept in C3 is that data is not “active”. This is to say there is no code associated with the data implicitly unlike constructors and destructors in an object oriented language. Not having constructors / destructors prevents RAII-style resource handling, but also allows the code to assume the memory can be freely allocated and initialized as it sees fit, without causing any corruption or undefined behaviour.
There is a fundamental difference between active objects and inert data, each has its advantages and disadvantages. C3 follows the C model, which is that data is passive and does not enforce any behaviour. This has very deep implications on the semantics of the language and adding constructors and destructors would change the language greatly, requiring modification of many parts of the language altering.
For that reason constructors and destructors will not be considered for C3.
Unicode identifiers
The main argument for unicode identifiers is that “it allows people to code in their own language”. However, there is no proof that this actually is used in practice. Furthermore there are practical issues, such as bidirectional text, characters with different code points that are rendered in an identical way etc.
Given the complexity and the lack of actual proven benefit, unicode identifiers will not happen for C3.
title: Grammar description: Grammar sidebar:
order: 999
Keywords
The following are reserved keywords used by C3:
void bool char double
float float16 int128 ichar
int iptr isz long
short uint128 uint ulong
uptr ushort usz float128
any anyfault typeid assert
asm bitstruct break case
catch const continue def
default defer distinct do
else enum extern false
fault for foreach foreach_r
fn tlocal if inline
import macro module nextcase
null return static struct
switch true try union
var while
$alignof $assert $case $default
$defined $echo $embed $exec
$else $endfor $endforeach $endif
$endswitch $eval $evaltype $error
$extnameof $for $foreach $if
$include $nameof $offsetof $qnameof
$sizeof $stringify $switch $typefrom
$typeof $vacount $vatype $vaconst
$varef $vaarg $vaexpr $vasplat
The following attributes are built in:
@align @benchmark @bigendian @builtin
@cdecl @deprecated @dynamic @export
@extern @extname @inline @interface
@littleendian @local @maydiscard @naked
@nodiscard @noinit @noinline @noreturn
@nostrip @obfuscate @operator @overlap
@packed @priority @private @public
@pure @reflect @section @stdcall
@test @unused @used @veccall
@wasm @weak @winmain
The following constants are defined:
$$BENCHMARK_FNS $$BENCHMARK_NAMES $$DATE
$$FILE $$FILEPATH $$FUNC
$$FUNCTION $$LINE $$LINE_RAW
$$MODULE $$TEST_FNS $$TEST_NAMES
$$TIME
Yacc grammar
%{
#include <stdio.h>
#define YYERROR_VERBOSE
int yydebug = 1;
extern char yytext[];
extern int column;
int yylex(void);
void yyerror(char *s);
%}
%token IDENT HASH_IDENT CT_IDENT CONST_IDENT
%token TYPE_IDENT CT_TYPE_IDENT
%token AT_TYPE_IDENT AT_IDENT CT_INCLUDE
%token STRING_LITERAL INTEGER
%token INC_OP DEC_OP SHL_OP SHR_OP LE_OP GE_OP EQ_OP NE_OP
%token AND_OP OR_OP MUL_ASSIGN DIV_ASSIGN MOD_ASSIGN ADD_ASSIGN
%token SUB_ASSIGN SHL_ASSIGN SHR_ASSIGN AND_ASSIGN
%token XOR_ASSIGN OR_ASSIGN VAR NUL ELVIS NEXTCASE ANYFAULT
%token MODULE IMPORT DEF EXTERN
%token CHAR SHORT INT LONG FLOAT DOUBLE CONST VOID USZ ISZ UPTR IPTR ANY
%token ICHAR USHORT UINT ULONG BOOL INT128 UINT128 FLOAT16 FLOAT128 BFLOAT16
%token TYPEID BITSTRUCT STATIC BANGBANG AT_CONST_IDENT HASH_TYPE_IDENT
%token STRUCT UNION ENUM ELLIPSIS DOTDOT BYTES
%token CT_ERROR
%token CASE DEFAULT IF ELSE SWITCH WHILE DO FOR CONTINUE BREAK RETURN FOREACH_R FOREACH
%token FN FAULT MACRO CT_IF CT_ENDIF CT_ELSE CT_SWITCH CT_CASE CT_DEFAULT CT_FOR CT_FOREACH CT_ENDFOREACH
%token CT_ENDFOR CT_ENDSWITCH BUILTIN IMPLIES INITIALIZE FINALIZE CT_ECHO CT_ASSERT CT_EVALTYPE CT_VATYPE
%token TRY CATCH SCOPE DEFER LVEC RVEC OPTELSE CT_TYPEFROM CT_TYPEOF TLOCAL
%token CT_VASPLAT INLINE DISTINCT CT_VACONST CT_NAMEOF CT_VAREF CT_VACOUNT CT_VAARG
%token CT_SIZEOF CT_STRINGIFY CT_QNAMEOF CT_OFFSETOF CT_VAEXPR
%token CT_EXTNAMEOF CT_EVAL CT_DEFINED CT_CHECKS CT_ALIGNOF ASSERT
%token ASM CHAR_LITERAL REAL TRUE FALSE CT_CONST_IDENT
%token LBRAPIPE RBRAPIPE HASH_CONST_IDENT
%start translation_unit
%%
path
: IDENT SCOPE
| path IDENT SCOPE
;
path_const
: path CONST_IDENT
| CONST_IDENT
;
path_ident
: path IDENT
| IDENT
;
path_at_ident
: path AT_IDENT
| AT_IDENT
;
ident_expr
: CONST_IDENT
| IDENT
| AT_IDENT
;
local_ident_expr
: CT_IDENT
| HASH_IDENT
;
ct_call
: CT_ALIGNOF
| CT_DEFINED
| CT_EXTNAMEOF
| CT_NAMEOF
| CT_OFFSETOF
| CT_QNAMEOF
;
ct_analyse
: CT_EVAL
| CT_SIZEOF
| CT_STRINGIFY
;
ct_arg
: CT_VACONST
| CT_VAARG
| CT_VAREF
| CT_VAEXPR
;
flat_path
: primary_expr param_path
| type
| primary_expr
;
maybe_optional_type
: optional_type
| empty
;
string_expr
: STRING_LITERAL
| string_expr STRING_LITERAL
;
bytes_expr
: BYTES
| bytes_expr BYTES
;
expr_block
: LBRAPIPE opt_stmt_list RBRAPIPE
;
base_expr
: string_expr
| INTEGER
| bytes_expr
| NUL
| BUILTIN CONST_IDENT
| BUILTIN IDENT
| CHAR_LITERAL
| REAL
| TRUE
| FALSE
| path ident_expr
| ident_expr
| local_ident_expr
| type initializer_list
| type '.' access_ident
| type '.' CONST_IDENT
| '(' expr ')'
| expr_block
| ct_call '(' flat_path ')'
| ct_arg '(' expr ')'
| ct_analyse '(' expr ')'
| CT_VACOUNT
| CT_CHECKS '(' expression_list ')'
| lambda_decl compound_statement
;
primary_expr
: base_expr
| initializer_list
;
range_loc
: expr
| '^' expr
;
range_expr
: range_loc DOTDOT range_loc
| range_loc DOTDOT
| DOTDOT range_loc
| range_loc ':' range_loc
| ':' range_loc
| range_loc ':'
| DOTDOT
;
call_inline_attributes
: AT_IDENT
| call_inline_attributes AT_IDENT
;
call_invocation
: '(' call_arg_list ')'
| '(' call_arg_list ')' call_inline_attributes
;
access_ident
: IDENT
| AT_IDENT
| HASH_IDENT
| CT_EVAL '(' expr ')'
| TYPEID
;
call_trailing
: '[' range_loc ']'
| '[' range_expr ']'
| call_invocation
| call_invocation compound_statement
| '.' access_ident
| INC_OP
| DEC_OP
| '!'
| BANGBANG
;
call_stmt_expr
: base_expr
| call_stmt_expr call_trailing
;
call_expr
: primary_expr
| call_expr call_trailing
;
unary_expr
: call_expr
| unary_op unary_expr
;
unary_stmt_expr
: call_stmt_expr
| unary_op unary_expr
;
unary_op
: '&'
| AND_OP
| '*'
| '+'
| '-'
| '~'
| '!'
| INC_OP
| DEC_OP
| '(' type ')'
;
mult_op
: '*'
| '/'
| '%'
;
mult_expr
: unary_expr
| mult_expr mult_op unary_expr
;
mult_stmt_expr
: unary_stmt_expr
| mult_stmt_expr mult_op unary_expr
;
shift_op
: SHL_OP
| SHR_OP
;
shift_expr
: mult_expr
| shift_expr shift_op mult_expr
;
shift_stmt_expr
: mult_stmt_expr
| shift_stmt_expr shift_op mult_expr
;
bit_op
: '&'
| '^'
| '|'
;
bit_expr
: shift_expr
| bit_expr bit_op shift_expr
;
bit_stmt_expr
: shift_stmt_expr
| bit_stmt_expr bit_op shift_expr
;
additive_op
: '+'
| '-'
;
additive_expr
: bit_expr
| additive_expr additive_op bit_expr
;
additive_stmt_expr
: bit_stmt_expr
| additive_stmt_expr additive_op bit_expr
;
relational_op
: '<'
| '>'
| LE_OP
| GE_OP
| EQ_OP
| NE_OP
;
relational_expr
: additive_expr
| relational_expr relational_op additive_expr
;
relational_stmt_expr
: additive_stmt_expr
| relational_stmt_expr relational_op additive_expr
;
rel_or_lambda_expr
: relational_expr
| lambda_decl IMPLIES relational_expr
;
and_expr
: relational_expr
| and_expr AND_OP relational_expr
;
and_stmt_expr
: relational_stmt_expr
| and_stmt_expr AND_OP relational_expr
;
or_expr
: and_expr
| or_expr OR_OP and_expr
;
or_stmt_expr
: and_stmt_expr
| or_stmt_expr OR_OP and_expr
;
or_expr_with_suffix
: or_expr
| or_expr '?'
| or_expr '?' '!'
;
or_stmt_expr_with_suffix
: or_stmt_expr
| or_stmt_expr '?'
| or_stmt_expr '?' '!'
;
ternary_expr
: or_expr_with_suffix
| or_expr '?' expr ':' ternary_expr
| or_expr_with_suffix ELVIS ternary_expr
| or_expr_with_suffix OPTELSE ternary_expr
| lambda_decl implies_body
;
ternary_stmt_expr
: or_stmt_expr_with_suffix
| or_stmt_expr '?' expr ':' ternary_expr
| or_stmt_expr_with_suffix ELVIS ternary_expr
| or_stmt_expr_with_suffix OPTELSE ternary_expr
| lambda_decl implies_body
;
assignment_op
: '='
| ADD_ASSIGN
| SUB_ASSIGN
| MUL_ASSIGN
| DIV_ASSIGN
| MOD_ASSIGN
| SHL_ASSIGN
| SHR_ASSIGN
| AND_ASSIGN
| XOR_ASSIGN
| OR_ASSIGN
;
empty
:
;
assignment_expr
: ternary_expr
| CT_TYPE_IDENT '=' type
| unary_expr assignment_op assignment_expr
;
assignment_stmt_expr
: ternary_stmt_expr
| CT_TYPE_IDENT '=' type
| unary_stmt_expr assignment_op assignment_expr
;
implies_body
: IMPLIES expr
;
lambda_decl
: FN maybe_optional_type fn_parameter_list opt_attributes
;
expr_no_list
: assignment_stmt_expr
;
expr
: assignment_expr
;
constant_expr
: ternary_expr
;
param_path_element
: '[' expr ']'
| '[' expr DOTDOT expr ']'
| '.' IDENT
;
param_path
: param_path_element
| param_path param_path_element
;
arg : param_path '=' expr
| type
| param_path '=' type
| expr
| CT_VASPLAT '(' range_expr ')'
| CT_VASPLAT '(' ')'
| ELLIPSIS expr
;
arg_list
: arg
| arg_list ',' arg
;
call_arg_list
: arg_list
| arg_list ';'
| arg_list ';' parameters
| ';'
| ';' parameters
| empty
;
opt_arg_list_trailing
: arg_list
| arg_list ','
| empty
;
enum_constants
: enum_constant
| enum_constants ',' enum_constant
;
enum_list
: enum_constants
| enum_constants ','
;
enum_constant
: CONST_IDENT
| CONST_IDENT '(' arg_list ')'
| CONST_IDENT '(' arg_list ',' ')'
;
identifier_list
: IDENT
| identifier_list ',' IDENT
;
enum_param_decl
: type
| type IDENT
| type IDENT '=' expr
;
base_type
: VOID
| BOOL
| CHAR
| ICHAR
| SHORT
| USHORT
| INT
| UINT
| LONG
| ULONG
| INT128
| UINT128
| FLOAT
| DOUBLE
| FLOAT16
| BFLOAT16
| FLOAT128
| IPTR
| UPTR
| ISZ
| USZ
| ANYFAULT
| ANY
| TYPEID
| TYPE_IDENT
| path TYPE_IDENT
| CT_TYPE_IDENT
| CT_TYPEOF '(' expr ')'
| CT_TYPEFROM '(' constant_expr ')'
| CT_VATYPE '(' constant_expr ')'
| CT_EVALTYPE '(' constant_expr ')'
;
type
: base_type
| type '*'
| type '[' constant_expr ']'
| type '[' ']'
| type '[' '*' ']'
| type LVEC constant_expr RVEC
| type LVEC '*' RVEC
;
optional_type
: type
| type '!'
;
local_decl_after_type
: CT_IDENT
| CT_IDENT '=' constant_expr
| IDENT opt_attributes
| IDENT opt_attributes '=' expr
;
local_decl_storage
: STATIC
| TLOCAL
;
decl_or_expr
: var_decl
| optional_type local_decl_after_type
| expr
;
var_decl
: VAR IDENT '=' expr
| VAR CT_IDENT '=' expr
| VAR CT_IDENT
| VAR CT_TYPE_IDENT '=' type
| VAR CT_TYPE_IDENT
;
initializer_list
: '{' opt_arg_list_trailing '}'
;
ct_case_stmt
: CT_CASE constant_expr ':' opt_stmt_list
| CT_CASE type ':' opt_stmt_list
| CT_DEFAULT ':' opt_stmt_list
;
ct_switch_body
: ct_case_stmt
| ct_switch_body ct_case_stmt
;
ct_for_stmt
: CT_FOR '(' for_cond ')' opt_stmt_list CT_ENDFOR
;
ct_foreach_stmt
: CT_FOREACH '(' CT_IDENT ':' expr ')' opt_stmt_list CT_ENDFOREACH
| CT_FOREACH '(' CT_IDENT ',' CT_IDENT ':' expr ')' opt_stmt_list CT_ENDFOREACH
;
ct_switch
: CT_SWITCH '(' constant_expr ')'
| CT_SWITCH '(' type ')'
| CT_SWITCH
;
ct_switch_stmt
: ct_switch ct_switch_body CT_ENDSWITCH
;
var_stmt
: var_decl ';'
decl_stmt_after_type
: local_decl_after_type
| decl_stmt_after_type ',' local_decl_after_type
;
declaration_stmt
: const_declaration
| local_decl_storage optional_type decl_stmt_after_type ';'
| optional_type decl_stmt_after_type ';'
;
return_stmt
: RETURN expr ';'
| RETURN ';'
;
catch_unwrap_list
: relational_expr
| catch_unwrap_list ',' relational_expr
;
catch_unwrap
: CATCH catch_unwrap_list
| CATCH IDENT '=' catch_unwrap_list
| CATCH type IDENT '=' catch_unwrap_list
;
try_unwrap
: TRY rel_or_lambda_expr
| TRY IDENT '=' rel_or_lambda_expr
| TRY type IDENT '=' rel_or_lambda_expr
;
try_unwrap_chain
: try_unwrap
| try_unwrap_chain AND_OP try_unwrap
| try_unwrap_chain AND_OP rel_or_lambda_expr
;
default_stmt
: DEFAULT ':' opt_stmt_list
;
case_stmt
: CASE expr ':' opt_stmt_list
| CASE expr DOTDOT expr ':' opt_stmt_list
| CASE type ':' opt_stmt_list
;
switch_body
: case_stmt
| default_stmt
| switch_body case_stmt
| switch_body default_stmt
;
cond_repeat
: decl_or_expr
| cond_repeat ',' decl_or_expr
;
cond
: try_unwrap_chain
| catch_unwrap
| cond_repeat
| cond_repeat ',' try_unwrap_chain
| cond_repeat ',' catch_unwrap
;
else_part
: ELSE if_stmt
| ELSE compound_statement
;
if_stmt
: IF optional_label paren_cond '{' switch_body '}'
| IF optional_label paren_cond '{' switch_body '}' else_part
| IF optional_label paren_cond statement
| IF optional_label paren_cond compound_statement else_part
;
expr_list_eos
: expression_list ';'
| ';'
;
cond_eos
: cond ';'
| ';'
;
for_cond
: expr_list_eos cond_eos expression_list
| expr_list_eos cond_eos
;
for_stmt
: FOR optional_label '(' for_cond ')' statement
;
paren_cond
: '(' cond ')'
;
while_stmt
: WHILE optional_label paren_cond statement
;
do_stmt
: DO optional_label compound_statement WHILE '(' expr ')' ';'
| DO optional_label compound_statement ';'
;
optional_label_target
: CONST_IDENT
| empty
;
continue_stmt
: CONTINUE optional_label_target ';'
;
break_stmt
: BREAK optional_label_target ';'
;
nextcase_stmt
: NEXTCASE CONST_IDENT ':' expr ';'
| NEXTCASE expr ';'
| NEXTCASE CONST_IDENT ':' type ';'
| NEXTCASE type ';'
| NEXTCASE ';'
;
foreach_var
: optional_type '&' IDENT
| optional_type IDENT
| '&' IDENT
| IDENT
;
foreach_vars
: foreach_var
| foreach_var ',' foreach_var
;
foreach_stmt
: FOREACH optional_label '(' foreach_vars ':' expr ')' statement
: FOREACH_R optional_label '(' foreach_vars ':' expr ')' statement
;
defer_stmt
: DEFER statement
| DEFER TRY statement
| DEFER CATCH statement
;
ct_if_stmt
: CT_IF constant_expr ':' opt_stmt_list CT_ENDIF
| CT_IF constant_expr ':' opt_stmt_list CT_ELSE opt_stmt_list CT_ENDIF
;
assert_expr
: try_unwrap_chain
| expr
;
assert_stmt
: ASSERT '(' assert_expr ')' ';'
| ASSERT '(' assert_expr ',' expr ')' ';'
;
asm_stmts
: asm_stmt
| asm_stmts asm_stmt
;
asm_instr
: INT
| IDENT
| INT '.' IDENT
| IDENT '.' IDENT
;
asm_addr
: asm_expr
| asm_expr additive_op asm_expr
| asm_expr additive_op asm_expr '*' INTEGER
| asm_expr additive_op asm_expr '*' INTEGER additive_op INTEGER
| asm_expr additive_op asm_expr shift_op INTEGER
| asm_expr additive_op asm_expr additive_op INTEGER
;
asm_expr
: CT_IDENT
| CT_CONST_IDENT
| IDENT
| '&' IDENT
| CONST_IDENT
| REAL
| INTEGER
| '(' expr ')'
| '[' asm_addr ']'
asm_exprs
: asm_expr
| asm_exprs ',' asm_expr
;
asm_stmt
: asm_instr asm_exprs ';'
| asm_instr ';'
;
asm_block_stmt
: ASM '(' expr ')'
| ASM '{' asm_stmts '}'
| ASM '{' '}'
;
/* Order here matches compiler */
statement
: compound_statement
| var_stmt
| declaration_stmt
| return_stmt
| if_stmt
| while_stmt
| defer_stmt
| switch_stmt
| do_stmt
| for_stmt
| foreach_stmt
| continue_stmt
| break_stmt
| nextcase_stmt
| asm_block_stmt
| ct_echo_stmt
| ct_assert_stmt
| ct_if_stmt
| ct_switch_stmt
| ct_foreach_stmt
| ct_for_stmt
| expr_no_list ';'
| assert_stmt
| ';'
;
compound_statement
: '{' opt_stmt_list '}'
;
statement_list
: statement
| statement_list statement
;
opt_stmt_list
: statement_list
| empty
;
switch_stmt
: SWITCH optional_label '{' switch_body '}'
| SWITCH optional_label '{' '}'
| SWITCH optional_label paren_cond '{' switch_body '}'
| SWITCH optional_label paren_cond '{' '}'
;
expression_list
: decl_or_expr
| expression_list ',' decl_or_expr
;
optional_label
: CONST_IDENT ':'
| empty
;
ct_assert_stmt
: CT_ASSERT constant_expr ':' constant_expr ';'
| CT_ASSERT constant_expr ';'
| CT_ERROR constant_expr ';'
;
ct_include_stmt
: CT_INCLUDE string_expr ';'
;
ct_echo_stmt
: CT_ECHO constant_expr ';'
bitstruct_declaration
: BITSTRUCT TYPE_IDENT ':' type opt_attributes bitstruct_body
bitstruct_body
: '{' '}'
| '{' bitstruct_defs '}'
| '{' bitstruct_simple_defs '}'
;
bitstruct_defs
: bitstruct_def
| bitstruct_defs bitstruct_def
;
bitstruct_simple_defs
: base_type IDENT ';'
| bitstruct_simple_defs base_type IDENT ';'
;
bitstruct_def
: base_type IDENT ':' constant_expr DOTDOT constant_expr ';'
| base_type IDENT ':' constant_expr ';'
;
static_declaration
: STATIC INITIALIZE opt_attributes compound_statement
| STATIC FINALIZE opt_attributes compound_statement
;
attribute_name
: AT_IDENT
| AT_TYPE_IDENT
| path AT_TYPE_IDENT
;
attribute_operator_expr
: '&' '[' ']'
| '[' ']' '='
| '[' ']'
;
attr_param
: attribute_operator_expr
| constant_expr
;
attribute_param_list
: attr_param
| attribute_param_list ',' attr_param
;
attribute
: attribute_name
| attribute_name '(' attribute_param_list ')'
;
attribute_list
: attribute
| attribute_list attribute
;
opt_attributes
: attribute_list
| empty
;
trailing_block_param
: AT_IDENT
| AT_IDENT '(' ')'
| AT_IDENT '(' parameters ')'
;
macro_params
: parameters
| parameters ';' trailing_block_param
| ';' trailing_block_param
| empty
;
macro_func_body
: implies_body ';'
| compound_statement
;
macro_declaration
: MACRO macro_header '(' macro_params ')' opt_attributes macro_func_body
;
struct_or_union
: STRUCT
| UNION
;
struct_declaration
: struct_or_union TYPE_IDENT opt_attributes struct_body
;
struct_body
: '{' struct_declaration_list '}'
;
struct_declaration_list
: struct_member_decl
| struct_declaration_list struct_member_decl
;
enum_params
: enum_param_decl
| enum_params ',' enum_param_decl
;
enum_param_list
: '(' enum_params ')'
| '(' ')'
| empty
;
struct_member_decl
: type identifier_list opt_attributes ';'
| struct_or_union IDENT opt_attributes struct_body
| struct_or_union opt_attributes struct_body
| BITSTRUCT ':' type opt_attributes bitstruct_body
| BITSTRUCT IDENT ':' type opt_attributes bitstruct_body
| INLINE type IDENT opt_attributes ';'
| INLINE type opt_attributes ';'
;
enum_spec
: ':' type enum_param_list
| empty
;
enum_declaration
: ENUM TYPE_IDENT enum_spec opt_attributes '{' enum_list '}'
;
faults
: CONST_IDENT
| faults ',' CONST_IDENT
;
fault_declaration
: FAULT TYPE_IDENT opt_attributes '{' faults '}'
| FAULT TYPE_IDENT opt_attributes '{' faults ',' '}'
;
func_macro_name
: IDENT
| AT_IDENT
;
func_header
: optional_type type '.' func_macro_name
| optional_type func_macro_name
;
macro_header
: func_header
| type '.' func_macro_name
| func_macro_name
;
fn_parameter_list
: '(' parameters ')'
| '(' ')'
;
parameters
: parameter '=' expr
| parameter
| parameters ',' parameter
| parameters ',' parameter '=' expr
;
parameter
: type IDENT opt_attributes
| type ELLIPSIS IDENT opt_attributes
| type ELLIPSIS CT_IDENT
| type CT_IDENT
| type ELLIPSIS opt_attributes
| type HASH_IDENT opt_attributes
| type '&' IDENT opt_attributes
| type opt_attributes
| '&' IDENT opt_attributes
| HASH_IDENT opt_attributes
| ELLIPSIS
| IDENT opt_attributes
| IDENT ELLIPSIS opt_attributes
| CT_IDENT
| CT_IDENT ELLIPSIS
;
func_definition
: FN func_header fn_parameter_list opt_attributes ';'
| FN func_header fn_parameter_list opt_attributes macro_func_body
;
const_declaration
: CONST CONST_IDENT opt_attributes '=' expr ';'
| CONST type CONST_IDENT opt_attributes '=' expr ';'
;
func_typedef
: FN optional_type fn_parameter_list
;
opt_distinct_inline
: DISTINCT
| DISTINCT INLINE
| INLINE DISTINCT
| INLINE
| empty
;
generic_parameters
: bit_expr
| type
| generic_parameters ',' bit_expr
| generic_parameters ',' type
;
typedef_type
: func_typedef
| type opt_generic_parameters
;
multi_declaration
: ',' IDENT
| multi_declaration ',' IDENT
;
global_storage
: TLOCAL
| empty
;
global_declaration
: global_storage optional_type IDENT opt_attributes ';'
| global_storage optional_type IDENT multi_declaration opt_attributes ';'
| global_storage optional_type IDENT opt_attributes '=' expr ';'
;
opt_tl_stmts
: top_level_statements
| empty
;
tl_ct_case
: CT_CASE constant_expr ':' opt_tl_stmts
| CT_CASE type ':' opt_tl_stmts
| CT_DEFAULT ':' opt_tl_stmts
;
tl_ct_switch_body
: tl_ct_case
| tl_ct_switch_body tl_ct_case
;
define_attribute
: AT_TYPE_IDENT '(' parameters ')' opt_attributes '=' '{' opt_attributes '}'
| AT_TYPE_IDENT opt_attributes '=' '{' opt_attributes '}'
;
opt_generic_parameters
: '<' generic_parameters '>'
| empty
;
define_ident
: IDENT '=' path_ident opt_generic_parameters
| CONST_IDENT '=' path_const opt_generic_parameters
| AT_IDENT '=' path_at_ident opt_generic_parameters
;
define_declaration
: DEF define_ident ';'
| DEF define_attribute ';'
| DEF TYPE_IDENT opt_attributes '=' opt_distinct_inline typedef_type ';'
;
tl_ct_if
: CT_IF constant_expr ':' opt_tl_stmts CT_ENDIF
| CT_IF constant_expr ':' opt_tl_stmts CT_ELSE opt_tl_stmts CT_ENDIF
;
tl_ct_switch
: ct_switch tl_ct_switch_body CT_ENDSWITCH
;
module_param
: CONST_IDENT
| TYPE_IDENT
;
module_params
: module_param
| module_params ',' module_param
;
module
: MODULE path_ident opt_attributes ';'
| MODULE path_ident '<' module_params '>' opt_attributes ';'
;
import_paths
: path_ident
| path_ident ',' path_ident
;
import_decl
: IMPORT import_paths opt_attributes ';'
;
translation_unit
: top_level_statements
| empty
;
top_level_statements
: top_level
| top_level_statements top_level
;
opt_extern
: EXTERN
| empty
;
top_level
: module
| import_decl
| opt_extern func_definition
| opt_extern const_declaration
| opt_extern global_declaration
| ct_assert_stmt
| ct_echo_stmt
| ct_include_stmt
| tl_ct_if
| tl_ct_switch
| struct_declaration
| fault_declaration
| enum_declaration
| macro_declaration
| define_declaration
| static_declaration
| bitstruct_declaration
;
%%
void yyerror(char *s)
{
fflush(stdout);
printf("\n%*s\n%*s\n", column, "^", column, s);
}
int main(int argc, char *argv[])
{
yyparse();
return 0;
}
title: C3 Specification description: C3 Specification sidebar:
order: 999
THIS SPECIFICATION IS UNDER DEVELOPMENT
Notation
The syntax is specified using Extended Backus-Naur Form (EBNF):
production ::= PRODUCTION_NAME '::=' expression?
expression ::= alternative ("|" alternative)*
alternative ::= term term*
term ::= PRODUCTION_NAME | TOKEN | set | group | option | repetition
set ::= '[' (range | CHAR) (rang | CHAR)* ']'
range ::= CHAR '-' CHAR
group ::= '(' expression ')'
option ::= expression '?'
repetition ::= expression '*'
Productions are expressions constructed from terms and the following operators, in increasing precedence:
| alternation
() grouping
? option (0 or 1 times)
* repetition (0 to n times)
Uppercase production names are used to identify lexical tokens. Non-terminals are in lower case. Lexical tokens are enclosed in single quotes ‘’.
The form a..b
represents the set of characters from a through b as alternatives.
Source code representation
A program consists of one or more translation units stored in files written in the Unicode character set, stored as a sequence of bytes using the UTF-8 encoding. Except for comments and the contents of character and string literals, all input elements are formed only from the ASCII subset (U+0000 to U+007F) of Unicode.
A raw byte stream is translated into a sequence of tokens which white space and comments are discarded. Doc comments may optionally be discarded as well. The resulting input elements form the tokens that are the terminal symbols of the syntactic grammar.
Lexical Translations
A raw byte stream is translated into a sequence of tokens which white space and comments are discarded. Doc comments may optionally be discarded as well. The resulting input elements form the tokens that are the terminal symbols of the syntactic grammar.
The longest possible translation is used at each step, even if the result does not ultimately make a correct program while another lexical translation would.
Example:
a--b
is translated asa
,--
,b
, which does not form a grammatically correct expression, even though the tokenizationa
,-
,-
,b
could form a grammatically correct expression.
Line Terminators
The C3 compiler divides the sequence of input bytes into lines by recognizing line terminators
Lines are terminated by the ASCII LF character (U+000A), also known as “newline”. A line termination specifies the termination of the // form of a comment.
Input Elements and Tokens
An input element may be:
- White space
- Comment
- Doc Contract
- Token
A token may be:
- Identifier
- Keyword
- Literal
- Separator
- Operator
A Doc Contract consists of:
- A stream of descriptive text
- A list of directive Tokens
Those input elements that are not white space or comments are tokens. The tokens are the terminal symbols of the
syntactic grammar. Whitespace and comments can serve to separate tokens that might be tokenized in another manner. For
example the characters +
and =
may form the operator token +=
only if there is no intervening white space or
comment.
White Space
White space is defined as the ASCII horizontal tab character (U+0009), form feed character (U+000A), vertical tab ( U+000B), carriage return (U+000D), space character (U+0020) and the line terminator character (U+000D).
WHITESPACE ::= [ \t\f\v\r\n]
Letters and digits
UC_LETTER ::= [A-Z]
LC_LETTER ::= [a-z]
LETTER ::= UC_LETTER | LC_LETTER
DIGIT ::= [0-9]
HEX_DIGIT ::= [0-9a-fA-F]
BINARY_DIGIT ::= [01]
OCTAL_DIGIT ::= [0-7]
LC_LETTER_US ::= LC_LETTER | "_"
UC_LETTER_US ::= UC_LETTER | "_"
ALPHANUM ::= LETTER | DIGIT
ALPHANUM_US ::= ALPHANUM | "_"
UC_ALPHANUM_US ::= UC_LETTER_US | DIGIT
LC_ALPHANUM_US ::= LC_LETTER_US | DIGIT
Comments
There are three types of regular comments:
// text
a line comment. The text between//
and line end is ignored./* text */
block comments. The text between/*
and*/
is ignored. It has nesting behaviour, so for every/*
discovered between the first/*
and the last*/
a corresponding*/
must be found.
Doc contract
<* text *>
doc block comment. The text between<*
and*>
is optionally parsed using the doc comment syntactic grammar. A compiler may choose to read<* text *>
as a regular comment.
Identifiers
Identifiers name program entities such as variables and types. An identifier is a sequence of one or more letters and digits. The first character in an identifier must be a letter or underscore.
C3 has three types of identifiers: const identifiers - containing only underscore and upper-case letters, type identifiers - starting with an upper case letter followed by at least one underscore letter and regular identifiers, starting with a lower case letter.
IDENTIFIER ::= "_"* LC_LETTER ALPHANUM_US*
CONST_IDENT ::= "_"* UC_LETTER UC_ALPHANUM_US*
TYPE_IDENT ::= "_"* UC_LETTER UC_ALPHANUM_US* LC_LETTER ALPHANUM_US*
CT_IDENT ::= "$" IDENTIFIER
CT_CONST_IDENT ::= "$" CONST_IDENT
CT_TYPE_IDENT ::= "$" TYPE_IDENT
AT_TYPE_IDENT ::= "@" TYPE_IDENT
PATH_SEGMENT ::= "_"* LC_LETTER LC_ALPHANUM_US*
Keywords
The following keywords are reserved and may not be used as identifiers:
asm any anyfault
assert attribute break
case cast catch
const continue default
defer def do
else enum extern
errtype false fn
generic if import
inline macro
module nextcase null
public return struct
switch true try
typeid var void
while
bool quad double
float long ulong
int uint byte
short ushort char
isz usz float16
float128
$and $assert $case
$default $echo $else
$error $endfor $endforeach
$endif $endswitch $for
$foreach $if $switch
$typef $vaarg $vaconst
$vacount $varef $vatype
Operators and punctuation
The following character sequences represent operators and punctuation.
& @ ~ | ^ :
, / $ . ; )
> < # { } -
( ) * [ ] %
>= <= + += -= !
? ?: && ?? &= |=
^= /= .. == ({ })
[< >] (< >) ++ --
%= != || :: << >>
!! ... <<= >>=
Integer literals
An integer literal is a sequence of digits representing an integer constant. An optional prefix sets a non-decimal base: 0b or 0B for binary, 0o, or 0O for octal, and 0x or 0X for hexadecimal. A single 0 is considered a decimal zero. In hexadecimal literals, letters a through f and A through F represent values 10 through 15.
For readability, an underscore character _ may appear after a base prefix or between successive digits; such underscores do not change the literal’s value.
INTEGER ::= DECIMAL_LIT | BINARY_LIT | OCTAL_LIT | HEX_LIT
DECIMAL_LIT ::= '0' | [1-9] ('_'* DECIMAL_DIGITS)?
BINARY_LIT ::= '0' [bB] '_'* BINARY_DIGITS
OCTAL_LIT ::= '0' [oO] '_'* OCTAL_DIGITS
HEX_LIT ::= '0' [xX] '_'* HEX_DIGITS
BINARY_DIGIT ::= [01]
HEX_DIGIT ::= [0-9a-fA-F]
DECIMAL_DIGITS ::= DIGIT ('_'* DIGIT)*
BINARY_DIGITS ::= BINARY_DIGIT ('_'* BINARY_DIGIT)*
OCTAL_DIGITS ::= OCTAL_DIGIT ('_'* OCTAL_DIGIT)*
HEX_DIGITS ::= HEX_DIGIT ('_'* HEX_DIGIT)*
42
4_2
0_600
0o600
0O600 // second character is capital letter 'O'
0xBadFace
0xBad_Face
0x_67_7a_2f_cc_40_c6
170141183460469231731687303715884105727
170_141183_460469_231731_687303_715884_105727
0600 // Invalid, non zero decimal number may not start with 0
_42 // an identifier, not an integer literal
42_ // invalid: _ must separate successive digits
0_xBadFace // invalid: _ must separate successive digits
Floating point literals
A floating-point literal is a decimal or hexadecimal representation of a floating-point constant.
A decimal floating-point literal consists of an integer part (decimal digits), a decimal point, a fractional part (decimal digits), and an exponent part (e or E followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; one of the decimal point or the exponent part may be elided. An exponent value exp scales the mantissa (integer and fractional part) by powers of 10.
A hexadecimal floating-point literal consists of a 0x or 0X prefix, an integer part (hexadecimal digits), a radix point, a fractional part (hexadecimal digits), and an exponent part (p or P followed by an optional sign and decimal digits). One of the integer part or the fractional part may be elided; the radix point may be elided as well, but the exponent part is required. An exponent value exp scales the mantissa (integer and fractional part) by powers of 2.
For readability, an underscore character _ may appear after a base prefix or between successive digits; such underscores do not change the literal value.
FLOAT_LIT ::= DEC_FLOAT_LIT | HEX_FLOAT_LIT
DEC_FLOAT_LIT ::= DECIMAL_DIGITS '.' DECIMAL_DIGITS? DEC_EXPONENT?
| DECIMAL_DIGITS DEC_EXPONENT
| '.' DECIMAL_DIGITS DEC_EXPONENT?
DEC_EXPONENT ::= [eE] [+-]? DECIMAL_DIGITS
HEX_FLOAT_LIT ::= '0' [xX] HEX_MANTISSA HEX_EXPONENT
HEX_MANTISSA ::= HEX_DIGITS '.' HEX_DIGITS?
| HEX_DIGITS
| '.' HEX_DIGITS
HEX_EXPONENT ::= [pP] [+-] DECIMAL_DIGITS
Characters
Characters are the fundamental components of strings and character literals.
CHAR_ELEMENT ::= [\x20-\x26] | [\x28-\x5B] | [\x5D-\x7F]
CHAR_LIT_BYTE ::= CHAR_ELEMENT | \x5C CHAR_ESCAPE
CHAR_ESCAPE ::= [abefnrtv\'\"\\]
| 'x' HEX_DIGIT HEX_DIGIT
UNICODE_CHAR ::= unicode_char
| 'u' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
| 'U' HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
HEX_DIGIT HEX_DIGIT HEX_DIGIT HEX_DIGIT
Backslash escapes
The following backslash escapes are available for characters and string literals:
\0 0x00 zero value
\a 0x07 alert/bell
\b 0x08 backspace
\e 0x1B escape
\f 0x0C form feed
\n 0x0A newline
\r 0x0D carriage return
\t 0x09 horizontal tab
\v 0x0B vertical tab
\\ 0x5C backslash
\' 0x27 single quote '
\" 0x22 double quote "
\x Escapes a single byte hex value
\u Escapes a two byte unicode hex value
\U Escapes a four byte unicode hex value
String literals
A string literal represents a string constant obtained from concatenating a sequence of characters.
String literals are character sequences between double quotes, as in “bar”. Within the quotes,
any character may appear except newline and unescaped double quote. The text between the
quotes forms the value of the literal, with backslash escapes interpreted as they are in
rune literals, with the same restrictions. The two-digit hexadecimal (\xnn) escapes represent
individual bytes of the resulting string; all other escapes represent the (possibly multibyte)
UTF-8 encoding of individual characters. Thus inside a string literal \xFF
represent a single
byte of value 0xFF
= 255, while ÿ
, \u00FF
, \U000000FF
and \xc3\xbf
represent the two bytes
0xc3 0xbf
of the UTF-8 encoding of character U+00FF
.
STRING_LIT ::= \x22 (CHAR_LIT_BYTE | UNICODE_CHAR)* \x22
Compile time string concatenation
Strings will concatenate if declared in sequence.
Example:
String s = "abc" "def" "ghi";
// This is equivalent to:
String s = "abcdefghi";
Raw string literals
Raw string literals are enclosed between `` and consist of the raw UTF8 in the source code between the “`”. A sequence of two “`” will be interpreted as a single escaped “`” that does not terminate the literal.
Compile time concatenation
Raw strings will concatenate with other regular strings and raw strings ( see string literal compile time concatenation).
Source code pre-filtering
The source code will pre-filter \r
(0x0D
) from the source code. This means that it is also implicitly
filtered out of raw strings.
Character literals
A character literal is enclosed in '
and may either consist of 1, 2, 4, 8, 16 bytes.
CHARACTER_LIT ::= "'" (CHAR_LIT_BYTE+) | UNICODE_CHAR "'"
Types
Types consist of built-in types and user-defined types (enums, structs, unions, bitstructs, fault and distinct).
Boolean types
bool
may have the two values true
and false
. It holds a single bit of information but is
stored in a char
type.
Integer types
The built-in integer types:
char unsigned 8-bit
ichar signed 8-bit
ushort unsigned 16-bit
short signed 16-bit
uint unsigned 32-bit
int signed 32-bit
ulong unsigned 64-bit
long signed 64-bit
uint128 unsigned 128-bit
int128 singed 128-bit
In addition, the following type aliases exist:
uptr unsigned pointer size
iptr signed pointer size
usz unsigned pointer offset / object size
isz signed pointer offset / object size
Floating point types
Built-in floating point types:
float16 IEEE 16-bit*
bfloat16 Brainfloat*
float IEEE 32-bit
double IEEE 64-bit
float128 IEEE 128-bit*
(* optionally supported)
Vector types
A vector lowers to the platform’s vector types where available. A vector has a base type and a width.
vector_type ::= type "[<" length ">]"
Vector base type
The base type of a vector must be boolean, an integer or a floating point type.
Min width
The vector width must be at least 1.
Element access
Vector elements are accessed using []
. It is possible to take the address of a single element.
Alignment
Alignment of vectors are platform dependent, but is at least the alignment of its element type.
Vector operations
Vectors support the same arithmetics as its underlying type, and will perform the operation element-wise.
Example:
int[<2>] a = { 1, 3 };
int[<2>] b = { 2, 7 };
int[<2>] c = a * b;
// Equivalent to
int[<2>] c = { a[0] * b[0], a[1] * b[1] };
Array types
An array has the alignment of its elements. An array must have at least one element.
Slice types
The slice consist of a pointer, followed by an usz length, having the alignment of pointers.
Pointer types
A pointer is the address to memory.
pointer_type ::= type "*"
Pointee type
The type of the memory pointed to is the pointee type. It may be any runtime type.
iptr and uptr
A pointer may be losslessly cast to an iptr
or uptr
. An iptr
or uptr
may be cast to a pointer of any type.
The wildcard pointer void*
The void*
may implicitly cast into any other pointer type. The void*
[implicitly casts into any other pointer.
A void* pointer may never be dereferenced.
Pointer arithmetic on void*
Performing pointer arithmetics on void* will assume that the element size is 1. This includes pointer arithmetics using subscripting.
Subscripting
Subscripting a pointer is equal to performing pointer arithmetics using the index, followed by a deref. Subscripts on pointers may be negative and will never do bounds checks.
Deref
Dereferencing a pointer will return the value in the memory location interpreted as the pointee type.
#
Struct types
A struct may not have zero members.
Alignment
A non-packed struct has the alignment of the member that has the highest alignment. A packed struct has alignment 1. See align attribute for details on changing the alignment.
Union types
A union may not have zero members.
Alignment
A union has the alignment of the member that has the highest alignment. See align attribute for details on changing the alignment.
Fault types
A fault is an extensible enum which can be used to create an Excuse for an empty optional.
Alignment
A fault type has the same alignment as a pointer. See align attribute for details on changing the alignment.
Enum types
Function types
Typeid type
The typeid is a pointer sized value which uniquely identifies a type.
Any type
The any
is a fat pointer (2 pointers wide) holding a pointer to a value and its corresponding typeid.
It cannot be dereferenced.
Fields
.ptr
returns a void*
pointer to the underlying value .type
returns the typeid
of the underlying value.
Switching over any
Switching over an any
value creates an any switch.
Anyfault type
Declarations and scope
Expressions
Assignment expression
assignment_expr ::= ct_type_assign | unary_expr assignment_op expr
ct_type_assign ::= ct_type_ident "=" type
assignment_op ::= "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "<<=" | ">>=" | "&=" | "^=" | "|="
Type assign
This assigns a new type to a compile time type variable. The value of the expression is the type assigned.
Combined assign
All assignment operations except for “=” are combined assign operation. They first perform the operation indicated
by the leftmost character(s) in the operator (e.g +
for +=
, <<
for <<=
etc) with the lhs and the rhs.
The result is then assigned to the left hand side. The result of the operation is the new value of the left
hand side.
Implicit conversion
If the left hand side is a pointer and the operation is “+=” or “-=” an attempt to implicitly convert to isz/usz will be tried.
For all other types and operations, an implicit conversion of rhs to the type of lhs will be tried.
Ternary, elvis and or-else expressions
ternary_group_expr ::= suffix_group_expr | ternary_expr | elvis_expr | orelse_expr
ternary_expr ::= or_expr "?" expr ":" ternary_group_expr
elvis_expr ::= suffix_expr "?:" ternary_group_expr
orelse_expr ::= suffix_expr "??" ternary_group_expr
Ternary evaluation
The most left-hand expression is evaluated to a boolean. If it is true, the value of the middle expression is returned, otherwise the last expression is returned.
Only the most left-hand expression and the returned expressions are evaluated.
The middle and last expression are implicitly converted to their unified type.
The resulting type is the unified type.
Elvis evaluation
Lhs and rhs are implicitly converted to their unified type.
The lhs is evaluated, it is then converted to a boolean, if the result it true, return the lhs value before its boolean conversion. Otherwise return the right hand side.
The right hand side is only evaluated if the lhs evaluates to false.
The resulting type is the unified type.
Orelse evaluation
The lhs must be optional. The non-optional type for lhs and rhs are calculated. The unified type of the result is calculated. Lhs are converted to the unified type preserving their optionality.
At runtime, lhs is evaluated. If it evaluates to an optional, rhs is returned instead.
Rhs is only evaluated if lhs evaluates to an optional.
The resulting type of the orelse is the post conversion type of the rhs.
Suffix expression
Suffix expressions convert a fault to an optional.
suffix_group_exp ::= or_group_expr | suffix_expr
suffix_expr ::= or_group_expr "?" "!"?
Effect of “?”
The “?” will convert the expression into an optional. The left hand side must be a fault type.
If an optional “!” follows, this optional is immediately returned, as if by a return <expr>?
statement.
Type of the expression
The type is a wildcard optional. If “!” is added, it is a wildcard type.
Rethrow expression
If the expression is optional, implicitly return with the optional value.
rethrow_expr ::= expr "!"
The expression to rethrow
The expression must have an optional type, otherwise this is a compile time error.
Type
The type of “rethrow” is the inner expr type without optional.
Relational expression
rel_group_expr ::= add_group_expr | relational_expr
relational_expr ::= rel_group_expr relational_op add_group_expr
relational_op ::= "<" | ">" | "<=" | ">="
TODO
And expression
This binary expression evaluates the lhs, and if the result is true
evaluates the rhs. The
result is true if both lhs and rhs are true.
and_group_expr ::= rel_group_expr | and_expr
and_expr ::= and_group_expr "&&" rel_group_expr
Type
The type of the and-expression is bool
.
Or expression
This binary expression evaluates the lhs, and if the result is false
evaluates the rhs. The
result is true if lhs or rhs is true.
or_group_expr ::= and_group_expr | or_expr
or_expr ::= or_group_expr "||" and_group_expr
Constant folded arithmetics
Constant folding will happen for constant integers and floating. Vectors operations will not be constant-folded.
Constant folded operations are: multiplication, division, addition, subtraction, bit shifts, bit negation, bitwise and, or and xor, comparison, logical and/or, and negation.
Type
The type of the or-expression is bool
.
Casts
Pointer casts
Integer to pointer cast
Any integer of pointer size or larger may be explicitly cast to a pointer. An integer to pointer cast is considered
non-constant, except in the special case where the integer == 0. In that case, the result is constant null
.
Example:
byte a = 1;
int* b = (int*)a; // Invalid, pointer type is > 8 bits.
int* c = (int*)1; // Valid, but runtime value.
int* d = (int*)0; // Valid and constant value.
Pointer to integer cast
A pointer may be cast to any integer, truncating the pointer value if the size of the pointer is larger than the pointer size. A pointer to integer cast is considered non-constant, except in the special case of a null pointer, where it is equal to the integer value 0.
Example:
fn void test() { ... }
def VoidFunc = fn void test();
VoidFunc a = &test;
int b = (int)null;
int c = (int)a; // Invalid, not constant
int d = (int)((int*)1); // Invalid, not constant
Subscript operator
The subscript operator may take as its left side a pointer, array or slice. The index may be of any integer
type. TODO
NOTE The subscript operator is not symmetrical as in C. For example in C3 array[n] = 33
is allowed, but
not n[array] = 33
. This is a change from C.
Operands
Compound Literals
Compound literals have the format
compound_literal ::= TYPE_IDENTIFIER '(' initializer_list ')'
initializer_list ::= '{' (initializer_param (',' initializer_param)* ','?)? '}'
initializer_param ::= expression | designator '=' expression
designator ::= array_designator | range_designator | field_designator
array_designator ::= '[' expression ']'
range_designator ::= '[' range_expression ']'
field_designator ::= IDENTIFIER
range_expression ::= (range_index)? '..' (range_index)?
range_index ::= expression | '^' expression
Taking the address of a compound literal will yield a pointer to stack allocated temporary.
Function calls
Function argument resolution
Call slots are in order: regular slots, vaarg slot, name-only slots.
No regular slots may appear after the vaarg slot, however there may be named parameters with default values after the vaarg slot if it’s not a raw vaarg.
These “name-only” slots need to have a parameter name and a default value, and may only be called as named arguments.
Named arguments may never be splat expressions.
- Step through all the arguments, resolve the named arguments and determine if there are any regular arguments.
- If there are regular arguments, then named arguments may only be in name-only slots, otherwise it is an error.
- If there are named arguments in the regular slots, all slots not provided arguments must have default values.
- Proceed with evaluation of arguments from left to right in call invocation order.
- Regular arguments are placed in the regular slots from left to right.
- If a regular argument is a splat expression, evaluate it without inference and determine if it is an array, vector, untyped list or slice with a known size, otherwise it is an error.
- A regular argument splat will be expanded into as many slots as its length, this may expand into vaarg arguments.
- In the vaarg slot, splatting a slice will forward it.
- In the vaarg slot, splatting an array, vector or untyped list will expand its elements as if they were provided as arguments.
- A named argument may never appear more than once.
- The vaarg slot may never be accessed using named arguments.
Varargs
For varargs, a bool
or any integer smaller than what the C ABI specifies for the c int
type is cast to int
. Any
float smaller than a double is cast to double
. Compile time floats will be cast to double. Compile time integers will
be cast to c int
type.
Statements
stmt ::= compound_stmt | non_compound_stmt
non_compound_stmt ::= assert_stmt | if_stmt | while_stmt | do_stmt | foreach_stmt | foreach_r_stmt
| for_stmt | return_stmt | break_stmt | continue_stmt | var_stmt
| declaration_stmt | defer_stmt | nextcase_stmt | asm_block_stmt
| ct_echo_stmt | ct_error_stmt | ct_assert_stmt | ct_if_stmt | ct_switch_stmt
| ct_for_stmt | ct_foreach_stmt | expr_stmt
Asm block statement
An asm block is either a string expression or a brace enclosed list of asm statements.
asm_block_stmt ::= "asm" ("(" constant_expr ")" | "{" asm_stmt* "}")
asm_stmt ::= asm_instr asm_exprs? ";"
asm_instr ::= ("int" | IDENTIFIER) ("." IDENTIFIER)
asm_expr ::= CT_IDENT | CT_CONST_IDENT | "&"? IDENTIFIER | CONST_IDENT | FLOAT_LITERAL
| INTEGER | "(" expr ")" | "[" asm_addr "]"
asm_addr ::= asm_expr (additive_op asm_expr asm_addr_trail?)?
asm_addr_trail ::= "*" INTEGER (additive_op INTEGER)? | (shift_op | additive_op) INTEGER
TODO
Assert statement
The assert statement will evaluate the expression and call the panic function if it evaluates to false.
assert_stmt ::= "assert" "(" expr ("," assert_message)? ")" ";"
assert_message ::= constant_expr ("," expr)*
Conditional inclusion
assert
statements are only included in “safe” builds. They may turn into assume directives for
the compiler on “fast” builds.
Assert message
The assert message is optional. It can be followed by an arbitrary number of expressions, in which case the message is understood to be a format string, and the following arguments are passed as values to the format function.
The assert message must be a compile time constant. There are no restriction on the format argument expressions.
Panic function
If the assert message has no format arguments or no assert message is included,
then the regular panic function is called. If it has format arguments then panicf
is called instead.
In the case the panicf
function does not exist (for example, compiling without the standard library),
then the format and the format arguments will be ignored and the assert
will be treated
as if no assert message was available.
Break statement
A break statement exits a while
, for
, do
, foreach
or switch
scope. A labelled break
may also exit a labelled if
.
break_stmt ::= "break" label? ";"
Break labels
If a break has a label, then it will instead exit an outer scope with the label.
Unreachable code
Any statement following break in the same scope is considered unreachable.
Compile time echo statement
During parsing, the compiler will output the text in the statement when it is semantically checked. The statement will be turned into a NOP statement after checking.
ct_echo_stmt ::= "$echo" constant_expr ";"
The message
The message must be a compile time constant string.
Compile time assert statement
During parsing, the compiler will check the compile time expression
and create a compile time error with the optional message. After
evaluation, the $assert
becomes a NOP statement.
ct_assert_stmt ::= "$assert" constant_expr (":" constant_expr) ";"
Evaluated expression
The checked expression must evaluate to a boolean compile time constant.
Error message
The second parameter, which is optional, must evaluate to a constant string.
Compile time error statement
During parsing, when semantically checked this statement will output a compile time error with the message given.
ct_error_stmt ::= "$error" constant_expr ";"
Error message
The parameter must evaluate to a constant string.
Compile time if statement
If the cond expression is true, the then-branch is processed by the compiler. If it evaluates to false, the else-branch is processed if it exists.
ct_if_stmt ::= "$if" constant_expr ":" stmt* ("$else" stmt*)? "$endif"
Cond expression
The cond expression must be possible to evaluate to true or false at compile time.
Scopes
The “then” and “else” branches will add a compile time scope that is exited when reaching $endif
.
It adds no runtime scope.
Evaluation
Statements in the branch not picked will not be semantically checked.
Compile time switch statement
ct_switch_stmt ::= "$switch" ("(" ct_expr_or_type ")")? ct_case_stmt+ "$endswitch"
ct_case_stmt ::= ("$default" | "$case" ct_expr_or_type) ":" stmt*
No cond expression switch
If the cond expression is missing, evaluation will go through each case until one case expression evaluates to true.
Type expressions
If a cond expression is a type, then all case statement expressions must be types as well.
Ranged cases
Compile time switch does not support ranged cases.
Fallthrough
If a case clause has no statements, then when executing the case, rather than exiting the switch, the next case clause immediately following it will be used. If that one should also be missing statements, the procedure will be repeated until a case clause with statements is encountered, or the end of the switch is reached.
Break and nextcase
Compile time switches do not support break
nor nextcase
.
Evaluation of statements
Only the case which is first matched has its statements processed by the compiler. All other statements are ignored and will not be semantically checked.
Continue statement
A continue statement jumps to the cond expression of a while
, for
, do
or foreach
continue_stmt ::= "continue" label? ";"
Continue labels
If a continue
has a label, then it will jump to the cond of the while/for/do in the outer scope
with the corresponding label.
Unreachable code
Any statement following continue
in the same scope is considered unreachable.
Declaration statement
A declaration statement adds a new runtime or compile time variable to the current scope. It is available after the declaration statement.
declaration_stmt ::= const_declaration | local_decl_storage? optional_type decls_after_type ";"
local_decl_storage ::= "tlocal" | "static"
decls_after_type ::= local_decl_after_type ("," local_decl_after_type)*
decl_after_type ::= CT_IDENT ("=" constant_expr)? | IDENTIFIER opt_attributes ("=" expr)?
Thread local storage
Using tlocal
allocates the runtime variable as a thread local variable. In effect this is the same as declaring
the variable as a global tlocal
variable, but the visibility is limited to the function. tlocal
may not be
combined with static
.
The initializer for a tlocal
variable must be a valid global init expression.
Static storage
Using static
allocates the runtime variable as a function global variable. In effect this is the same as declaring
a global, but visibility is limited to the function. static
may not be combined with tlocal
.
The initializer for a static
variable must be a valid global init expression.
Scopes
Runtime variables are added to the runtime scope, compile time variables to the compile time scope. See var statements .
Multiple declarations
If more than one variable is declared, no init expressions are allowed for any of the variables.
No init expression
If no init expression is provided, the variable is zero initialized.
Opt-out of zero initialization
Using the @noinit attribute opts out of zero initialization.
Self referencing initialization
An init expression may refer to the address of the same variable that is declared, but not the value of the variable.
Example:
void* a = &a; // Valid
int a = a + 1; // Invalid
Defer statement
The defer statements are executed at (runtime) scope exit, whether through return
, break
, continue
or rethrow.
defer_stmt ::= "defer" ("try" | "catch")? stmt
Defer in defer
The defer body (statement) may not be a defer statement. However, if the body is a compound statement then this may have any number of defer statements.
Static and tlocal variables in defer
Static and tlocal variables are allowed in a defer statement. Only a single variable is instantiated regardless of the number of inlining locations.
Defer and return
If the return
has an expression, then it is evaluated before the defer statements (due to exit from the current
function scope),
are executed.
Example:
int a = 0;
defer a++;
return a;
// This is equivalent to
int a = 0;
int temp = a;
a++;
return temp;
Defer and jump statements
A defer body may not contain a break
, continue
, return
or rethrow that would exit the statement.
Defer execution
Defer statements are executed in the reverse order of their declaration, starting from the last declared defer statement.
Defer try
A defer try
type of defer will only execute if the scope is left through normal fallthrough, break
,
continue
or a return
with a result.
It will not execute if the exit is through a rethrow or a return
with an optional value.
Defer catch
A defer catch
type of defer will only execute if the scope is left through a rethrow or a return
with an optional
value
It will not execute if the exit is a normal fallthrough, break
, continue
or a return
with a result.
Non-regular returns - longjmp, panic and other errors
Defers will not execute when doing longjmp
terminating through a panic
or other error. They
are only invoked on regular scope exits.
Expr statement
An expression statement evaluates an expression.
expr_stmt ::= expr ";"
No discard
If the expression is a function or macro call either returning an optional or annotated @nodiscard
, then
the expression is a compile time error. A function or macro returning an optional can use the @maydiscard
attribute to suppress this error.
If statement
An if statement will evaluate the cond expression, then execute the first statement (the “then clause”) in the if-body if it evaluates to “true”, otherwise execute the else clause. If no else clause exists, then the next statement is executed.
if_stmt ::= "if" (label ":")? "(" cond_expr ")" if_body
if_body ::= non_compound_stmt | compound_stmt else_clause? | "{" switch_body "}"
else_clause ::= "else" (if_stmt | compound_stmt)
Scopes
Both the “then” clause and the else clause open new scopes, even if they are non-compound statements. The cond expression scope is valid until the exit of the entire statement, so any declarations in the cond expression are available both in then and else clauses. Declarations in the “then” clause is not available in the else clause and vice versa.
Special parsing of the “then” clause
If the then-clause isn’t a compound statement, then it must follow on the same row as the cond expression. It may not appear on a consecutive row.
Break
It is possible to use labelled break to break out of an if statement. Note that an unlabelled break
may not
be used.
If-try
The cond expression may be a try-unwrap chain. In this case, the unwrapped variables are scoped to the “then” clause only.
If-catch
The cond expression may be a catch-unwrap. The unwrap is scoped to the “then” clause only. If one or more variables are in the catch, then the “else” clause have these variables implicitly unwrapped.
Example:
int! a = foo();
int! b = foo();
if (catch a, b)
{
// Do something
}
else
{
int x = a + b; // Valid, a and b are implicitly unwrapped.
}
If-catch implicit unwrap
If an if-catch’s “then”-clause will jump out of the outer scope in all code paths and the catch is on one or more variables, then this variable(s) will be implicitly unwrapped in the outer scope after the if-statement.
Example:
int! a = foo();
if (catch a)
{
return;
}
int x = a; // Valid, a is implicitly unwrapped.
Nextcase statement
Nextcase will jump to another switch
case.
nextcase_stmt ::= "nextcase" ((label ":")? (expr | "default"))? ";"
Labels
When a nextcase has a label, the jump is to the switch in an outer scope with the corresponding label.
No expression jumps
A nextcase
without any expression jumps to the next case clause in the current switch. It is not possible
to use no expression nextcase
with labels.
Jumps to default
Using default
jumps to the default clause of a switch.
Missing case
If the switch has constant case values, and the nextcase expression is constant, then the value of the expression must match a case clause. Not matching a case is a compile time error.
If one or more cases are non-constant and/or the nextcase expression is non-constant, then no compile time check is made.
Variable expression
If the nextcase has a non-constant expression, or the cases are not all constant, then first the nextcase expression is evaluated. Next, execution will proceed as if the switch was invoked again, but with the nextcase expression as the switch cond expression. See switch statement.
If the switch does not have a cond expression, nextcase with an expression is not allowed.
Unreachable code
Any statement in the same scope after a nextcase
are considered unreachable.
Switch statement
switch_stmt ::= "switch" (label ":")? ("(" cond_expr ")")? switch body
switch_body ::= "{" case_clause* "}"
case_clause ::= default_stmt | case_stmt
default_stmt ::= "default" ":" stmt*
case_stmt ::= "case" label? expr (".." expr)? ":" stmt*
Regular switch
If the cond expression exists and all case statements have constant expression, then first the cond expression is evaluated, next the case corresponding to the expression’s value will be jumped to and the statement will be executed. After reaching the end of the statements and a new case clause or the end of the switch body, the execution will jump to the first statement after the switch.
If-switch
If the cond expression is missing or the case statements are non-constant expressions, then each case clause will be evaluated in order after the cond expression has been evaluated (if it exists):
- If a cond expression exists, calculate the case expression and execute the case if it is matching the cond expression. A default statement has no expression and will always be considered matching the cond expression reached.
- If no con expression exists, calculate the case expression and execute the case if the expression evaluates to “true” when implicitly converted to boolean. A default statement will always be considered having the “true” result.
Any-switch
If the cond expression is an any
type, the switch is handled as if switching was done over the type
field of the any
. This field has the type of typeid, and the cases follows the rules
for switching over typeid.
If the cond expression is a variable, then this variable is implicitly converted to a pointer with the pointee type given by the case statement.
Example:
any a = abc();
switch (a)
{
case int:
int b = *a; // a is int*
case float:
float z = *a; // a is float*
case Bar:
Bar f = *a; // a is Bar*
default:
// a is not unwrapped
}
Ranged cases
Cases may be ranged. The start and end of the range must both be constant integer values. The start must be less or equal to the end value. Using non-integers or non-constant values is a compile time error.
Fallthrough
If a case clause has no statements, then when executing the case, rather than exiting the switch, the next case clause immediately following it will be executed. If that one should also be missing statement, the procedure will be repeated until a case clause with statements is encountered (and executed), or the end of the switch is reached.
Exhaustive switch
If a switch case has a default clause or it is switching over an enum and there exists a case for each enum value then the switch is exhaustive.
Break
If an unlabelled break, or a break with the switch’s label is encountered, then the execution will jump out of the switch and proceed directly after the end of the switch body.
Unreachable code
If a switch is exhaustive and all case clauses end with a jump instruction, containing no break statement out of the current switch, then the code directly following the switch will be considered unreachable.
Switching over typeid
If the switch cond expression is a typeid, then case declarations may use only the type name after the case,
which will be interpreted as having an implicit .typeid
. Example: case int:
will be interpreted as if
written case int.typeid
.
Nextcase without expression
Without a value nextcase
will jump to the beginning of the next case clause. It is not allowed to
put nextcase
without an expression if there are no following case clauses.
Nextcase with expression
Nextcase with an expression will evaluate the expression and then jump as if the switch was entered with the cond expression corresponding to the value of the nextcase expression. Nextcase with an expression cannot be used on a switch without a cond expression.
Do statement
The do statement first evaluates its body (inner statement), then evaluates the cond expression. If the cond expression evaluates to true, jumps back into the body and repeats the process.
do_stmt ::= "do" label? compound_stmt ("while" "(" cond_expr ")")? ";"
Unreachable code
The statement after a do
is considered unreachable if the cond expression cannot ever be false
and there is no break
out of the do.
Break
break
will exit the do with execution continuing on the following statement.
Continue
continue
will jump directly to the evaluation of the cond, as if the end of the statement had been reached.
Do block
If no while
part exists, it will only execute the block once, as if it ended with while (false)
, this is
called a “do block”
For statement
The for
statement will perform the (optional) init expression. The cond expression will then be tested. If
it evaluates to true
then the body will execute, followed by the incr expression. After execution will
jump back to the cond expression and execution will repeat until the cond expression evaluates to false
.
for_stmt ::= "for" label? "(" init_expr ";" cond_expr? ";" incr_expr ")" stmt
init_expr ::= decl_expr_list?
incr_expr ::= expr_list?
Init expression
The init expression is only executed once before the rest of the for loop is executed. Any declarations in the init expression will be in scope until the for loop exits.
The init expression may optionally be omitted.
Incr expression
The incr expression is evaluated before evaluating the cond expr every time except for the first one.
The incr expression may optionally be omitted.
Cond expression
The cond expression is evaluated every loop. Any declaration in the cond expression is scoped to the current loop, i.e. it will be reinitialized at the start of every loop.
The cond expression may optionally be omitted. This is equivalent to setting the cond expression to
always return true
.
Unreachable code
The statement after a for
is considered unreachable if the cond expression cannot ever be false, or is
omitted and there is no break
out of the loop.
Break
break
will exit the for
with execution continuing on the following statement after the for
.
Continue
continue
will jump directly to the evaluation of the cond, as if the end of the statement had been reached.
Equivalence of while
and for
A while
loop is functionally equivalent to a for
loop without init and incr expressions.
Foreach and foreach_r statements
The foreach
statement will loop over a sequence of values. The foreach_r
is equivalent to
foreach
but the order of traversal is reversed.
foreach
starts with element 0
and proceeds step by step to element len - 1
.
foreach_r
starts starts with element len - 1
and proceeds step by step to element 0
.
foreach_stmt ::= "foreach" label? "(" foreach_vars ":" expr ")" stmt
foreach_r_stmt ::= "foreach_r" label? "(" foreach_vars ":" expr ")" stmt
foreach_vars ::= (foreach_index ",")? foreach_var
foreach_var ::= type? "&"? IDENTIFIER
Break
break
will exit the foreach statement with execution continuing on the following statement after.
Continue
continue
will cause the next iteration to commence, as if the end of the statement had been reached.
Iteration by value or reference
Normally iteration are by value. Each element is copied into the foreach variable. If &
is added before the variable name, the elements will be retrieved by reference instead, and consequently
the type of the variable will be a pointer to the element type instead.
Foreach variable
The foreach variable may omit the type. In this case the type is inferred. If the type differs from the element type, then an implicit conversion will be attempted. Failing this is a compile time error.
Foreach index
If a variable name is added before the foreach variable, then this variable will receive the index of the element.
For foreach_r
this mean that the first value of the index will be len - 1
.
The index type defaults to usz
.
If an optional type is added to the index, the index will be converted to this type. The type must be an integer type. The conversion happens as if the conversion was a direct cast. If the actual index value would exceed the maximum representable value of the type, this does not affect the actual iteration, but may cause the index value to take on an incorrect value due to the cast.
For example, if the optional index type is char
and the actual index is 256
, then the index value would show 0
as (char)256
evaluates to zero.
Modifying the index variable will not affect the foreach iteration.
Foreach support
Foreach is natively supported for any slice, array, pointer to an array, vector and pointer to a vector. These types support both iteration by value and reference.
In addition, a type with operator overload for len
and []
will support iteration by value,
and a type with operator overload for len
and &[]
will support iteration by reference.
Return statement
The return statement evaluates its expression (if present) and returns the result.
return_stmt ::= "return" expr? ";"
Jumps in return statements
If the expression should in itself cause an implicit return, for example due to the rethrow operator !
, then this
jump will happen before the return.
An example:
return foo()!;
// is equivalent to:
int temp = foo()!;
return temp;
Return from expression blocks
A return
from an expression block only returns out of the expression block, it never returns from the
expression block’s enclosing scope.
Empty returns
An empty return is equivalent to a return with a void type. Consequently constructs like foo(); return;
and return (void)foo();
are equivalent.
Unreachable code
Any statement directly following a return in the same scope are considered unreachable.
While statement
The while statement evaluates the cond expression and executes the statement if it evaluates to true. After this the cond expression is evaluated again and the process is repeated until cond expression returns false.
while_stmt ::= "while" label? "(" cond_expr ")" stmt
Unreachable code
The statement after a while is considered unreachable if the cond expression cannot ever be false
and there is no break
out of the while.
Break
break
will exit the while with execution continuing on the following statement.
Continue
continue
will jump directly to the evaluation of the cond, as if the end of the statement had been reached.
Var statement
A var statement declares a variable with inferred type, or a compile time type variable. It can be used both for runtime and compile time variables. The use for runtime variables is limited to macros.
var_stmt ::= "var" IDENTIFIER | CT_IDENT | CT_TYPE_IDENT ("=" expr)? ";"
Inferring type
In the case of a runtime variable, the type is inferred from the expression. Not providing an expression is a compile time error. The expression must resolve to a runtime type.
For compile time variables, the expression is optional. The expression may resolve to a runtime or compile time type.
Scope
Runtime variables will follow the runtime scopes, identical to behaviour in a declaration statement. The compile
time variables will follow the compile time scopes which are delimited by scoping compile time
statements ($if
, $switch
,
$foreach
and $for
).
Attributes
Attributes are modifiers attached to modules, variables, type declarations etc.
name | used with |
---|---|
@align | fn, const, variables, user-defined types, struct member |
@benchmark | module, fn |
@bigendian | bitstruct only |
@builtin | macro, fn, global, constant |
@callconv | fn, call |
@deprecated | fn, macro, interface, variables, constants, user-defined types, struct member |
@dynamic | fn |
@export | fn, globals, constants, struct, union, enum, fault |
@extern | fn, globals, constants, user-defined types |
@if | all except local variables and calls |
@inline | fn, call |
@interface | fn |
@littleendian | bitstruct only |
@local | module, fn, macro, globals, constants, user-defined types, attributes and aliases |
@maydiscard | fn, macro |
@naked | fn |
@nodiscard | fn, macro |
@noinit | variables |
@noinline | fn, call |
@noreturn | fn, macro |
@nostrip | fn, globals, constants, struct, union, enum, fault |
@obfuscate | enum, fault |
@operator | fn, macro |
@optional | interface methods |
@overlap | bitstruct only |
@packed | struct, union |
@priority | initializer/finalizer |
@private | module, fn, macro, globals, constants, user-defined types, attributes and aliases |
@public | module, fn, macro, globals, constants, user-defined types, attributes and aliases |
@pure | call |
@reflect | fn, globals, constants, user-defined types |
@section | fn, globals, constants |
@test | module, fn |
@unused | all except call and initializer/finalizers |
@used | all except call and initializer/finalizers |
@weak | fn, globals, constants |
@winmain | fn |
@deprecated
Takes an optional constant string. If the node is in use, print the deprecation and add the optional string if present.
@optional
Marks an interface method as optional, and so does not need to be implemented by a conforming type.
@winmain
Marks a main
function as a win32 winmain function, which is the entrypoint for a windowed
application on Windows. This allows the main function to take a different set of
arguments than usual.
@callconv
@callconv
can
be used with a function or a call. It takes a constant string which is
either “veccall”, “stdcall” or “cdecl”. If more than one @callconv
is applied to a function or call, the last one takes precedence.
User defined attributes
User defined attributes group a list of attributes.
attribute_decl ::= "def" AT_TYPE_IDENT ("(" parameters ")")? attribute* "=" "{" attribute* "}" ";"
Empty list of attributes
The list of attributes may be empty.
Parameter arguments
Arguments given to user defined attributes will be passed on to the attributes in the list.
Expansion
When a user defined attribute is encountered, its list of attributes is copied and appended instead of the user defined attribute. Any argument passed to the attribute is evaluated and passed as a constant by the name of the parameter to the evaluation of the attribute parameters in the list.
Nesting
A user defined attribute can contain other user defined attributes. The definition may not be cyclic.
Methods
Operator overloading
@operator
overloads may only be added to user defined types (distinct, unions, struct, enum and fault).
Indexing operator ([])
This requires a return type and a method parameter, which is the index.
Reference indexing operator (&[])
This requires a return type and a method parameter, which is the index. If []
is implemented,
it should return a pointer to []
.
Assigning index operator (=[])
This has a void return type, and index should match that of []
and &[]
. Value should match that
of []
and be the pointee of the result of &[]
.
Len operator (len)
This must have an integer return type.
Dynamic methods
@dynamic
may be used on methods for any type except any
and interfaces.
Modules
Module paths are hierarchal, with each sub-path appended with ‘::’ + the name:
path ::= PATH_SEGMENT ("::" PATH_SEGMENT)
Each module declaration starts its own module section. All imports and all @local
declarations
are only visible in the current module section.
module_section ::= "module" path opt_generic_params? attributes? ";"
generic_param ::= TYPE_IDENT | CONST_IDENT
opt_generic_params ::= "(<" generic_param ("," generic_param)* ">)"
Any visibility attribute defined in a module section will be the default visibility in all declarations in the section.
If the @benchmark
attribute is applied to the module section then all function declarations
will implicitly have the @benchmark
attribute.
If the @test
attribute is applied to the module section then all function declarations
will implicitly have the @test
attribute.
title: Thank You description: Thank You sidebar:
order: 1000
Thank You
- A huge “thank you” goes out to all contributors and sponsors.
- A special mention to Huly® Platform™ for sponsoring $100/month.