From b847e830a062fc4b3a2c5a81835ac9b25f440d3b Mon Sep 17 00:00:00 2001 From: onrirr Date: Wed, 24 Jan 2024 15:44:46 +0300 Subject: [PATCH] almost done merging with the old docs --- .astro/types.d.ts | 245 +++ .../docs/references/docs/anyinterfaces.md | 246 +++ src/content/docs/references/docs/arrays.md | 184 ++ src/content/docs/references/docs/asm.md | 59 + .../docs/references/docs/attributes.md | 181 ++ src/content/docs/references/docs/builtins.md | 133 ++ .../docs/references/docs/changesfromc.md | 204 +++ src/content/docs/references/docs/cinterop.md | 67 + src/content/docs/references/docs/comments.md | 52 + src/content/docs/references/docs/compare.md | 195 ++ .../docs/references/docs/compiletime.md | 198 ++ src/content/docs/references/docs/contracts.md | 168 ++ .../docs/references/docs/conversion.md | 254 +++ src/content/docs/references/docs/define.md | 130 ++ src/content/docs/references/docs/examples.md | 639 +++++++ .../docs/references/docs/expressions.md | 132 ++ src/content/docs/references/docs/faq.md | 249 +++ src/content/docs/references/docs/functions.md | 372 ++++ src/content/docs/references/docs/generics.md | 71 + src/content/docs/references/docs/libraries.md | 70 + src/content/docs/references/docs/macros.md | 407 ++++ src/content/docs/references/docs/modules.md | 449 +++++ src/content/docs/references/docs/naming.md | 108 ++ src/content/docs/references/docs/operators.md | 81 + src/content/docs/references/docs/optionals.md | 575 ++++++ .../docs/references/docs/precedence.md | 56 + .../docs/references/docs/reflection.md | 334 ++++ src/content/docs/references/docs/sample.md | 290 +++ .../docs/references/docs/specification.md | 1631 +++++++++++++++++ .../docs/references/docs/standard_library.md | 428 +++++ .../docs/references/docs/statements.md | 154 ++ src/content/docs/references/docs/syntax.md | 1321 +++++++++++++ src/content/docs/references/docs/types.md | 637 +++++++ .../references/docs/undefinedbehaviour.md | 80 + src/content/docs/references/docs/variables.md | 23 + src/content/docs/references/docs/vectors.md | 67 + 36 files changed, 10490 insertions(+) create mode 100644 src/content/docs/references/docs/anyinterfaces.md create mode 100644 src/content/docs/references/docs/arrays.md create mode 100644 src/content/docs/references/docs/asm.md create mode 100644 src/content/docs/references/docs/attributes.md create mode 100644 src/content/docs/references/docs/builtins.md create mode 100644 src/content/docs/references/docs/changesfromc.md create mode 100644 src/content/docs/references/docs/cinterop.md create mode 100644 src/content/docs/references/docs/comments.md create mode 100644 src/content/docs/references/docs/compare.md create mode 100644 src/content/docs/references/docs/compiletime.md create mode 100644 src/content/docs/references/docs/contracts.md create mode 100644 src/content/docs/references/docs/conversion.md create mode 100644 src/content/docs/references/docs/define.md create mode 100644 src/content/docs/references/docs/examples.md create mode 100644 src/content/docs/references/docs/expressions.md create mode 100644 src/content/docs/references/docs/faq.md create mode 100644 src/content/docs/references/docs/functions.md create mode 100644 src/content/docs/references/docs/generics.md create mode 100644 src/content/docs/references/docs/libraries.md create mode 100644 src/content/docs/references/docs/macros.md create mode 100644 src/content/docs/references/docs/modules.md create mode 100644 src/content/docs/references/docs/naming.md create mode 100644 src/content/docs/references/docs/operators.md create mode 100644 src/content/docs/references/docs/optionals.md create mode 100644 src/content/docs/references/docs/precedence.md create mode 100644 src/content/docs/references/docs/reflection.md create mode 100644 src/content/docs/references/docs/sample.md create mode 100644 src/content/docs/references/docs/specification.md create mode 100644 src/content/docs/references/docs/standard_library.md create mode 100644 src/content/docs/references/docs/statements.md create mode 100644 src/content/docs/references/docs/syntax.md create mode 100644 src/content/docs/references/docs/types.md create mode 100644 src/content/docs/references/docs/undefinedbehaviour.md create mode 100644 src/content/docs/references/docs/variables.md create mode 100644 src/content/docs/references/docs/vectors.md diff --git a/.astro/types.d.ts b/.astro/types.d.ts index 7740ae2..4932a5f 100644 --- a/.astro/types.d.ts +++ b/.astro/types.d.ts @@ -192,6 +192,251 @@ declare module 'astro:content' { collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; +"references/docs/anyinterfaces.md": { + id: "references/docs/anyinterfaces.md"; + slug: "references/docs/anyinterfaces"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/arrays.md": { + id: "references/docs/arrays.md"; + slug: "references/docs/arrays"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/asm.md": { + id: "references/docs/asm.md"; + slug: "references/docs/asm"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/attributes.md": { + id: "references/docs/attributes.md"; + slug: "references/docs/attributes"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/builtins.md": { + id: "references/docs/builtins.md"; + slug: "references/docs/builtins"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/changesfromc.md": { + id: "references/docs/changesfromc.md"; + slug: "references/docs/changesfromc"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/cinterop.md": { + id: "references/docs/cinterop.md"; + slug: "references/docs/cinterop"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/comments.md": { + id: "references/docs/comments.md"; + slug: "references/docs/comments"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/compare.md": { + id: "references/docs/compare.md"; + slug: "references/docs/compare"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/compiletime.md": { + id: "references/docs/compiletime.md"; + slug: "references/docs/compiletime"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/contracts.md": { + id: "references/docs/contracts.md"; + slug: "references/docs/contracts"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/conversion.md": { + id: "references/docs/conversion.md"; + slug: "references/docs/conversion"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/define.md": { + id: "references/docs/define.md"; + slug: "references/docs/define"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/examples.md": { + id: "references/docs/examples.md"; + slug: "references/docs/examples"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/expressions.md": { + id: "references/docs/expressions.md"; + slug: "references/docs/expressions"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/faq.md": { + id: "references/docs/faq.md"; + slug: "references/docs/faq"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/functions.md": { + id: "references/docs/functions.md"; + slug: "references/docs/functions"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/generics.md": { + id: "references/docs/generics.md"; + slug: "references/docs/generics"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/libraries.md": { + id: "references/docs/libraries.md"; + slug: "references/docs/libraries"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/macros.md": { + id: "references/docs/macros.md"; + slug: "references/docs/macros"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/modules.md": { + id: "references/docs/modules.md"; + slug: "references/docs/modules"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/naming.md": { + id: "references/docs/naming.md"; + slug: "references/docs/naming"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/operators.md": { + id: "references/docs/operators.md"; + slug: "references/docs/operators"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/optionals.md": { + id: "references/docs/optionals.md"; + slug: "references/docs/optionals"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/precedence.md": { + id: "references/docs/precedence.md"; + slug: "references/docs/precedence"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/reflection.md": { + id: "references/docs/reflection.md"; + slug: "references/docs/reflection"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/sample.md": { + id: "references/docs/sample.md"; + slug: "references/docs/sample"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/specification.md": { + id: "references/docs/specification.md"; + slug: "references/docs/specification"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/standard_library.md": { + id: "references/docs/standard_library.md"; + slug: "references/docs/standard_library"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/statements.md": { + id: "references/docs/statements.md"; + slug: "references/docs/statements"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/syntax.md": { + id: "references/docs/syntax.md"; + slug: "references/docs/syntax"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/types.md": { + id: "references/docs/types.md"; + slug: "references/docs/types"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/undefinedbehaviour.md": { + id: "references/docs/undefinedbehaviour.md"; + slug: "references/docs/undefinedbehaviour"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/variables.md": { + id: "references/docs/variables.md"; + slug: "references/docs/variables"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; +"references/docs/vectors.md": { + id: "references/docs/vectors.md"; + slug: "references/docs/vectors"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".md"] }; "references/index.mdx": { id: "references/index.mdx"; slug: "references"; diff --git a/src/content/docs/references/docs/anyinterfaces.md b/src/content/docs/references/docs/anyinterfaces.md new file mode 100644 index 0000000..c81e794 --- /dev/null +++ b/src/content/docs/references/docs/anyinterfaces.md @@ -0,0 +1,246 @@ +--- +title: Any and interfaces +description: Any and interfaces +sidebar: + order: 122 +--- + +## 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*`. Note that it is a fat pointer and is two pointers wide (unlike `void*`). +It cannot be dereferenced. + +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.typeid) + { + 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.typeid.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. However, +methods marked `@optional` does not need to be implemented: + + interface VeryOptional + { + fn void test(); + fn void do_something(int x, void* ptr) @optional; + } + + struct Foo (VeryOptional) + { + int z; + } + + fn void Foo.test(&self) { } + +This example is would compile, despite not implementing both functions, as the second method is marked `@optional`. + +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* using) @dynamic + { + return string::printf("Baz(%d)", baz.x, .using = using); + } + +### "@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 + +A pointer to an interface e.g. `MyName*` is can be cast back and forth to `any*`, but only types which +implement the interface completely may implicitly be cast to the interface pointer. + +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. + diff --git a/src/content/docs/references/docs/arrays.md b/src/content/docs/references/docs/arrays.md new file mode 100644 index 0000000..454bb33 --- /dev/null +++ b/src/content/docs/references/docs/arrays.md @@ -0,0 +1,184 @@ +--- +title: Arrays +description: Arrays +sidebar: + order: 109 +--- + +Arrays has a central role in programming. C3 offers 2 built-in types of arrays: + +## Fixed arrays + +`[]` e.g. `int[4]`. These 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 x[3] = { 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] + + + +## Subarray + +The final type is the subarray `[]` e.g. `int[]`. A subarray 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: + + int[4] a = { 1, 2, 3, 4}; + int[] b = &a; // Implicit conversion is always ok. + int[4] c = (int[4])b; // Will copy the value of b into c. + int[4]* d = (int[4])a; // Equivalent to d = &a + b.len; // Returns 4 + e += 1; + int* f = b; // Equivalent to e = &a + f = d; // implicit conversion ok. + +### Slicing arrays + +It's possible to use a range syntax to create subarrays from pointers, arrays, vararrays and other subarrays. They either use range syntax: +`arr[..]` (the end index is included in the final result) or start + len syntax: `arr[ : len]` + + + int[5] a = { 1, 20, 50, 100, 200 }; + int[] b = a[0..4]; // The whole array as a slice. + int[] b2 = a[0:5]; // Same as above. + int[] c = a[2..3]; // { 50, 100 } + int[] c2 = a[2:2]; // Same as above. + +It's possible to omit the first and last index in ranges, and the start index for start + len. +Omitting the start index will default it to 0, omitting the end index will set it to the last valid +index (this is not allowed on pointers). Length cannot be omitted in start + len syntax. + +The following are all equivalent: + + 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]; + +One may also slice from the end. Again this is not allowed for pointers. + + int[5] a = { 1, 20, 50, 100, 200 }; + int[] b = a[1..^2]; // { 20, 50, 100 } + int[] c = a[^3..]; // { 50, 100, 200 } + int[] d = a[^3:2]; // { 50, 100 } + +One may also use 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 overlapping ranges, e.g. `a[1..2] = a[0..1]` is undefined 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 { * ptr; usz len; }`. + +There is a built-in struct `std::core::runtime::SubArrayContainer` which has the exact data layout of the fat array pointers. It is defined to be + + struct SubArrayContainer + { + void* ptr; + usz len; + } + +## Iteration over arrays + +Slices, fixed and variable arrays may all be iterated over using `foreach (Type x : array)`: + + int[4] a = { 1, 2, 3, 5 }; + foreach (int x : a) + { + ... + } + +Using `&` it is possible to get an element by reference rather than by copy. +Furthermore, by providing two variable name, the first is assumed to be the +index: + + Foo[4] a = { ... } + foreach (int idx, Foo* &f : a) + { + f.abc = idx; // Mutates the array element + } + +It is possible to enable foreach on any type +by implementing "len" and "[]" methods and annotating them using the `@operator` attribute: + + struct Vector + { + usz size; + int* elements; + } + + macro int Vector.get(Vector* vector, usz element) @operator([]) + { + return vector.elements[element]; + } + + macro usz Vector.size(Vector* vector) @operator(len) + { + return vector.size; + } + + Vector v; + v.add(3); + v.add(7); + + // Will print 3 and 7 + foreach (int i : v) + { + io::printfn("%d", i); + } + +For more information, see [operator overloading](../operators) \ No newline at end of file diff --git a/src/content/docs/references/docs/asm.md b/src/content/docs/references/docs/asm.md new file mode 100644 index 0000000..2e774c8 --- /dev/null +++ b/src/content/docs/references/docs/asm.md @@ -0,0 +1,59 @@ +--- +title: Inline Assembly +description: Inline Assembly +sidebar: + order: 132 +--- + +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: + +1. An identifier, e.g. `FOO`, `x`. +2. A numeric constant `1` `0xFF` etc. +3. A register name (always lower case with a '$' prefix) e.g. `$eax` `$r7`. +4. The address of a variable e.g. `&x`. +5. An indirect address: `[addr]` or `[addr + index * + offset]`. +6. 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 and aarch64 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.* \ No newline at end of file diff --git a/src/content/docs/references/docs/attributes.md b/src/content/docs/references/docs/attributes.md new file mode 100644 index 0000000..ccd1fca --- /dev/null +++ b/src/content/docs/references/docs/attributes.md @@ -0,0 +1,181 @@ +--- +title: Attributes +description: Attributes +sidebar: + order: 131 +--- + +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 + +### `@align(alignment)` (struct, bitstructs, union, var, fn) + +This attribute sets the minimum alignment for a field or a variable. + +### `@benchmark` (fn) + +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` (bitstruct) + +Lays out the bits as if the data was stored in a big endian type, regardless of host system endianness. + +### `@builtin` (fn, 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` (fn) + +Sets the call convention, which may be ignored if the convention is not supported on the target. +Valid arguments are `veccall`, `ccall`, `stdcall`. + +### `@deprecated` (types, fn, macro, global, const, member) + +Marks the particular type, global, const or member as deprecated, making use trigger a warning. + +### `@export` (fn, 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` (fn, global, const, enum, union, struct, fault) + +Sets the external (linkage) name of this declaration. + +### `@finalizer` (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` (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` (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` (fn, call) + +Declares a function to always be inlined or if placed on a call, that the call should be inlined. + +### `@littleendian` (bitstruct) + +Lays out the bits as if the data was stored in a little endian type, regardless of host system endianness. + +### `@local` (any declaration) + +Sets the visibility to "local", which means it's only visible in the current module section. + +### `@maydiscard` (fn, macro) + +Allows the return value of the function or macro to be discarded even if it is an optional. Should be +used sparingly. + +### `@naked` (fn) + +This attribute disables prologue / epilogue emission for the function. + +### `@nodiscard` (fn, macro) + +The return value may not be discarded. + +### `@noinit` (global, local) + +Prevents the compiler from zero initializing the variable. + +### `@noreturn` (fn) + +Declares that the function will never return. + +### `@nostrip` (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` (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` (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` (bitstruct) + +Allows bitstruct fields to have overlapping bit ranges. + +### `@packed` (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` (any declaration) + +Sets the visibility to "private", which means it is visible in the same module, but not from other modules. + +### `@pure` (call) + +Used to annotate a non pure function as "pure" when checking for conformance to `@pure` on +functions. + +### `@packed` (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` (any declaration) + +Adds additional reflection information. Has no effect currently. + +### `@section(name)` (fn, const, global) + +Declares that a global variable or function should appear in a specific section. + +### `@test` (fn) + +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` (any declaration) + +Marks the declaration as possibly unused (but should not emit a warning). + +### `@used` (any declaration) + +Marks a parameter, value etc. as must being used. + +### `@weak` (fn, 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 = {}; +``` diff --git a/src/content/docs/references/docs/builtins.md b/src/content/docs/references/docs/builtins.md new file mode 100644 index 0000000..4a73659 --- /dev/null +++ b/src/content/docs/references/docs/builtins.md @@ -0,0 +1,133 @@ +--- +title: Builtins +description: Builtins +sidebar: + order: 126 +--- +The compiler offers builtin constants and functions. Some are only available on certain targets. All builtins use the `$$` +name prefix. + +## Builtin constants + +These can all 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 "" on the global level. + +#### $$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. + + +## Builtin functions + +These functions are *not guaranteed* to exist on all platforms. They are intended for standard library +internal use, and typically the standard library has macros that wrap these builtins, so they should not be used on its own. + +#### $$trap + +Emits a trap instruction. + +#### $$unreachable + +Inserts an "unreachable" annotation. + +#### $$stacktrace + +Returns the current "callstack" reference if available. 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. + diff --git a/src/content/docs/references/docs/changesfromc.md b/src/content/docs/references/docs/changesfromc.md new file mode 100644 index 0000000..cd2d2f6 --- /dev/null +++ b/src/content/docs/references/docs/changesfromc.md @@ -0,0 +1,204 @@ +--- +title: Changes from C +description: Changes from C +sidebar: + order: 100 +--- + + +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](../precedence). + +##### Removal of the const type qualifier + +The const qualifier is only retained for actual constant variables. C3 uses a special type of [post condition](../contracts) 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](../conversion). + +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 to get the C behaviour. + +*Rationale: 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 +// 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 +// 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. \ No newline at end of file diff --git a/src/content/docs/references/docs/cinterop.md b/src/content/docs/references/docs/cinterop.md new file mode 100644 index 0000000..510c9e9 --- /dev/null +++ b/src/content/docs/references/docs/cinterop.md @@ -0,0 +1,67 @@ +--- +title: C Interoperability +description: C Interoperability +sidebar: + order: 133 +--- + +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!"); + } + +While C3 functions are available from C using their external name, it's often useful to +define an external name using `@extern` to match C usage. + + + module foo; + fn int square(int x) + { + return x * x; + } + + fn int square2(int x) @extern("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 ` 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 underlying type from C. +- C3 cannot use C bit fields +- C assumes the enum size is `CInt` +- C3 uses fixed integer sizes, this means that `int` and `CInt` does not need to be the same. +- Passing arrays by value like in C3 must be represented as passing a struct containing the array. +- Atomic types are not supported by C3. +- Volatile and const have no representation in C3. diff --git a/src/content/docs/references/docs/comments.md b/src/content/docs/references/docs/comments.md new file mode 100644 index 0000000..05e6bae --- /dev/null +++ b/src/content/docs/references/docs/comments.md @@ -0,0 +1,52 @@ +--- +title: Comments & Documentation +description: Comments & Documentation +sidebar: + order: 130 +--- +C3 uses three distinct comment types: + +1. The normal `//` line comment, which is terminated at the end of the line. +2. The classic `/* ... */` C style comment, but unlike in C they are allowed to nest. +3. Documentation comments `/** ... **/` the text within these comments will be parsed as documentation and optional contracts on the following code. + +## Documentation + +Documentation comments start with `/**` and must be terminated using `*/`. Note that any number of `*` may follow `/**` and any number of stars may preceed `*/`. Any space and `*` in the beginning of each line will be ignored. + +Here is an example: + +``` +/** + * Here are som docs. + * @param foo The number of foos. + * @required foo > 4 + * @deprecated + * @mycustom 2 + **/ +void bar(int foo) +{ + io::printf("%d", foo); +} +``` + +In the example above, the following is parsed as description: *"Here are the docs."*, then there is a description associated with the `foo` parameter: *"The number of foos"*. + +On top of that there are two annotations for the compiler: `@required foo > 4` which tells the compiler and a user of the function that a precondition is that `foo` must be greater than 4. It is also marked as @deprecated, which can be used to display warnings. + +Finally, a custom annotation, "@mycustom" is added. The compiler is free to silently ignore such annotations, but can optionally emit warnings for them, it is otherwise ignored. + +### Available annotations + +| Name | format | +|-------------|-------------------------------------:| +| @param | `@param ` | +| @return | `@return ` | +| @return! | `@return! , , ...` | +| @fails | `@fails ` | +| @deprecated | `@deprecated ` | +| @require | `@require , , ...` | +| @ensure | `@ensure , , ...` | +| @pure | `@pure` | + +See [contracts](../contracts) for information regarding `@require`, `@ensure`, `@const`, `@pure`, `@checked`. diff --git a/src/content/docs/references/docs/compare.md b/src/content/docs/references/docs/compare.md new file mode 100644 index 0000000..ce1e4f5 --- /dev/null +++ b/src/content/docs/references/docs/compare.md @@ -0,0 +1,195 @@ +--- +title: Comparisons with other languages +description: How C3 compares to other languages +sidebar: + order: 101 +--- +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 subarrays +- 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 subarrays +- 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 subarrays and +strings reduces the places where C3 is unsafe. Rust provides arrays and strings, +but they are not built in. Subarrays are the same as Rust's slices. + +##### 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. +- Tuple 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. +- Error handling support. +- 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. \ No newline at end of file diff --git a/src/content/docs/references/docs/compiletime.md b/src/content/docs/references/docs/compiletime.md new file mode 100644 index 0000000..de3587e --- /dev/null +++ b/src/content/docs/references/docs/compiletime.md @@ -0,0 +1,198 @@ +--- +title: Compile Time Evaluation +description: Compile time introspection and execution +sidebar: + order: 116 +--- +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 :` 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; + $endif + } + +Switching without argument is also allowed, which works like an if-else chain: + + macro foo($x, $y) + { + $switch + $case $x > 10: + $y += $x * $x; + $case $x < 0: + $y += $x; + $default: + $y -= $x; + $endif + } + +### 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: + +1. Non-conditional declarations are registered. +2. Conditional module sections are either discarded or have all of their non-conditional declarations registered. +3. 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](../reflection). + diff --git a/src/content/docs/references/docs/contracts.md b/src/content/docs/references/docs/contracts.md new file mode 100644 index 0000000..90ede78 --- /dev/null +++ b/src/content/docs/references/docs/contracts.md @@ -0,0 +1,168 @@ +--- +title: Contracts +description: Contracts +sidebar: + order: 117 +--- + +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. A pre-condition use the `@require` annotation. + +``` +/** + * @require foo > 0, foo < 1000 + **/ +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. + +| 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 badFunc(int* i) +{ + *i = 2; +} + +/** + * @param [in] i + */ +fn void lyingFunc(int* i) +{ + badFunc(i); // The compiler might not check this! +} + +fn void test() +{ + int a = 1; + lyingFunc(&a); + io::printf("%d", a); // Might print 1! +} +``` + +However, compilers will usually detect this: +``` + +/** + * @param [in] i + */ +fn void badFunc(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; + +type Secretfn fn void(); + +fn void badFunc() +{ + i = 2; +} + +Secretfn foo = nil; + +/** + * @pure + */ +fn void lyingFunc() +{ + SecretFunc(); // The compiler cannot reason about this! +} + +fn void test() +{ + foo = &badFunc; + i = 1; + lyingFunc(); + io::printf("%d", a); // Might print 1! +} +``` + +However, compilers will usually detect this: + +``` +int i = 0; + +fn void badFunc() +{ + i = 2; +} + +/** + * @pure + */ +fn void lyingFunc() +{ + badFunc(); // Error! Calling an impure function +} +``` + +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 $and($defined(resource.open), $defined(resource.open()) `Expected resource to have an "open" function` + * @require resource != nil + * @require $assignable(resource.open(), void*) + **/ +macro openResource(resource) +{ + return resource.open(); +} +``` diff --git a/src/content/docs/references/docs/conversion.md b/src/content/docs/references/docs/conversion.md new file mode 100644 index 0000000..e6e2dfb --- /dev/null +++ b/src/content/docs/references/docs/conversion.md @@ -0,0 +1,254 @@ +--- +title: Conversions and Promotions +description: Conversions and Promotions +sidebar: + order: 124 +--- + +C3 differs in some crucial respects when it comes to number conversions and promotions. These are the rules for C3: + +- float to int conversions require a cast +- int to float conversions do not require a cast +- bool to float converts to 0.0 / 1.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 to bool *do not* require a cast, any non zero float value considered true +- Implicit conversion to bool only occurs in conditionals or when the value is enclosed in `()` e.g. `bool x = (1.0)` or `if (1.0) { ... }` + +C3 uses two's complement arithmetic for all integer math. + +## 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: + +1. For any floating point type with a bitwidth smaller than 32 bits, widen to `float`. E.g. `f16 -> float` +2. 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. + +1. Shifts and assign look at the lhs expression. +2. `++`, `--`, `~`, `-`, `!!`, `!` - check the inner type. +3. `+`, `-`, `*`, `/`, `%`, `^`, `|`, `&`, `??`, `?:` - check both lhs and rhs. +4. Narrowing int/float cast, assume the type is the narrowed type. +5. Widening int/float cast, look at the inner expression, ignoring the cast. +6. In the case of any other cast, assume it is opaque and the type is that of the cast. +7. In the case of an integer literal, instead of looking at the type, check that the integer would fit the type to narrow to. +8. For .len access, allow narrowing to C int width. +9. 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: + +1. First perform implicit promotion. +2. If both types are the same, the maximum type is this type. +3. 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`. +4. If both types are floating point types, the maximum type is the widest floating point type. E.g. `float + double -> double`. +5. 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`. +6. 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` +7. 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) +8. 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: + +1. A substruct pointer may implicitly convert to a parent struct. +2. 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. +3. Substruct subarrays, vararrays 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](../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, so 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. + +1. Resolve the operands. +2. Find the maximum type of the two operands. +3. Promote both operands to the resulting type if both are simple expressions +4. The resulting type of the expression is the resulting type. + +### 2. Addition with left side being a pointer + +1. Resolve the operands. +2. If the rhs is not an integer, this is an error. +3. If the rhs has a bit width that exceeds isz, this is an error. +4. The result of the expression is the lhs type. + +### 3. Subtraction with lhs pointer and rhs integer + +1. Resolve the operands. +2. If the right hand type has a bit width that exceeds isz, this is an error. +3. The result of the expression is the left hand type. + +### 4. Subtraction with both sides pointers + +1. Resolve the operands. +2. If the either side is a `void *`, it is cast to the other type. +3. If the types of the sides are different, this is an error. +4. The result of the expression is isz. +5. If this result exceeds the target width, this is an error. + +### 6. Bit operations `^` `&` `|` + +These operations are only valid for integers and booleans. + +1. Resolve the operands. +2. Find the maximum type of the two operands. +3. Promote both operands to the maximum type if they are simple expressions. +4. The result of the expression is the maximum type. + +### 6. Shift operations `<<` `>>` + +These operations are only valid for integers. + +1. Resolve the operands. +2. In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. +3The result of the expression is the lhs type. + +### 7. Assignment operations `+=` `-=` `*=` `*=` `/=` `%=` `^=` `|=` `&=` + +1. Resolve the lhs. +2. Resolve the right operand as an assignment rhs. +3. The result of the expression is the lhs type. + +### 8. Assignment shift `>>=` `<<=` + +1. Resolve both operands +2. In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. +3. The result of the expression is the lhs type. + +### 9. `&&` and `||` + +1. Resolve both operands. +2. Insert bool cast of both operands. +3. The type is bool. + +### 10. `<=` `==` `>=` `!=` + +1. Resolve the operands, left to right. +2. Find the maximum type of the two operands. +3. Promote both operands to the maximum type. +4. The type is bool. + +## Unary conversions + +### 1. Bit negate + +1. Resolve the inner operand. +2. If the inner type is not an integer this is an error. +2. The type is the inner type. + +### 2. Boolean not + +1. Resolve the inner operand. +2. The type is bool. + +### 3. Negation + +1. Resolve the inner operand. +2. If the type inner type is not a number this is an error. +3. If the inner type is an unsigned integer, cast it to the same signed type. +4. The type is the type of the result from (3) + +### 4. `&` and `&&` + +1. Resolve the inner operand. +2. The type is a pointer to the type of the inner operand. + +### 5. `*` + +1. Resolve the inner operand. +2. If the operand is not a pointer, or is a `void *` pointer, this is an error. +3. The type is the pointee of the inner operand's type. + +Dereferencing 0 is implementation defined. + +### 6. `++` and `--` + +1. Resolve the inner operand. +2. If the type is not a number, this is an error. +3. The type is the same as the inner operand. + +## Base expressions + +### 1. Typed identifiers + +1. The type is that of the declaration. +2. If the width of the type is less than that of the target type, widen to the target type. +3. If the width of the type is greater than that of the target type, it is an error. + +### 2. Constants and literals + +1. 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 suffix `ixx` or `uxx` is given then it is considered a an integer of that type width and signedness. It cannot be implicitly narrowed. +2. If the constant is a floating point value, it is assumed to be a `double` unless suffixed with `f` which is then assumed to be a `float`. If a bit width is given after `f`, it is instead a floating point type of that width. \ No newline at end of file diff --git a/src/content/docs/references/docs/define.md b/src/content/docs/references/docs/define.md new file mode 100644 index 0000000..27cc984 --- /dev/null +++ b/src/content/docs/references/docs/define.md @@ -0,0 +1,130 @@ +--- +title: Define +description: The `def` statement +sidebar: + order: 108 +--- + +# The "def" statement + +The `def` statement in C3 is intended for aliasing identifiers and types. + +## Defining a type alias + +`def = ` 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. + + distinct Foo = distinct 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 = `. + +``` +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, functions +and variables: + + import generic_foo; + + // Parameterized function aliases + def int_foo_call = generic_foo::foo_call(); + def double_foo_call = generic_foo::foo_call(); + + // Parameterized type aliases + def IntFoo = Foo(); + def DoubleFoo = Foo(); + + // Parameterized global aliases + def int_max_foo = generic_foo::max_foo(); + def double_max_foo = generic_foo::max_foo(); + +For more information, see the chapter on [generics](../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"); + } + + 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 + } + diff --git a/src/content/docs/references/docs/examples.md b/src/content/docs/references/docs/examples.md new file mode 100644 index 0000000..eaccf11 --- /dev/null +++ b/src/content/docs/references/docs/examples.md @@ -0,0 +1,639 @@ +--- +title: Examples +description: Examples of C3 code +sidebar: + order: 102 +--- + +#####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 +``` +fn void example_foreach(float[] values) +{ + foreach (index, value : values) + { + io::printfn("%d: %f", index, value); + } +} +``` + + +#####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 + 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. + +Enums also define `.min` and `.max`, returning the minimum and maximum value for the enum values. `.values` returns an array with all enums. + +``` +enum State : uint +{ + START, + STOP, +} + +const uint LOWEST = State.min; +const uint HIGHEST = State.max; + +State start = State.values[0]; +``` + +#####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` to execute of success. + +``` +fn void! test(int x) +{ + defer io::printn(""); + defer io::printn("A"); + defer try io::printn("X"); + defer catch io::printn("B") + defer catch (err) io::printfn("%s", err.message); + if (x == 1) return FooError!; + print("!") +} + +test(0); // Prints "!XA" +test(1); // Prints "FOOBA" 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 printFile(String filename) +{ + String! file = io::load_file(filename); + + // The following function is not executed on error. + 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); + } +} +``` + +Read more about optionals and error handling [here](../optionals). + +##### 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](../contracts). + +##### 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](../macros). + +##### 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); +} +``` + +##### Compile time reflection and 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: + +```text +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; + +Read more about compile time execution [here](../compiletime). + +##### Generic modules + +Generic modules implements a generic system. + +``` +module stack(); +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(); + +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() 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](../generics) + +##### 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](../anyinterfaces). \ No newline at end of file diff --git a/src/content/docs/references/docs/expressions.md b/src/content/docs/references/docs/expressions.md new file mode 100644 index 0000000..3034605 --- /dev/null +++ b/src/content/docs/references/docs/expressions.md @@ -0,0 +1,132 @@ +--- +title: Expressions +description: Expressions +sidebar: + order: 113 +--- + +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: + +1. Binary expressions are evaluated from left to right. +2. Assignment occurs right to left, so `a = a++` would result in `a` being unchanged. +3. Call arguments are evaluated in parameter order. +4. For named parameters, evaluation is in parameter order, not argument order. So the evaluation order of +`foo(.a = x++, .b = x--)` depends on the declaration order of `a` and `b`. + +## 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 })); + +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: + +1. The `null` literal. +2. Boolean, floating point and integer literals. +3. The result of arithmetics on constant expressions. +4. Compile time variables (prefixed with `$`) +5. Global constant variables with initializers that are constant expressions. +6. The result of macros that does not generate code and only uses constant expressions. +7. 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. +8. String literals. +9. Initializer lists containing constant values. + +Some things that are *not* constant expressions: + +1. Any pointer that isn't the `null` literal, even if it's derived from a constant expression. +2. The result of a cast except for casts of constant expressions to a numeric type. +3. 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 to be 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; \ No newline at end of file diff --git a/src/content/docs/references/docs/faq.md b/src/content/docs/references/docs/faq.md new file mode 100644 index 0000000..88e0cde --- /dev/null +++ b/src/content/docs/references/docs/faq.md @@ -0,0 +1,249 @@ +--- +title: Frequently Asked Questions +description: Frequently asked questions about C3 +sidebar: + order: 105 +--- +## 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` contains `malloc` 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, `std::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:** 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 `mem::heap()` and do allocations directly. There is also a temporary allocator. + +**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. + +**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 = mem::temp(); + @pool(temp) + { + // Note, 'mem::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 `external` (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(int 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)`. + +## 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. + diff --git a/src/content/docs/references/docs/functions.md b/src/content/docs/references/docs/functions.md new file mode 100644 index 0000000..f8c74e5 --- /dev/null +++ b/src/content/docs/references/docs/functions.md @@ -0,0 +1,372 @@ +--- +title: Functions +description: Functions +sidebar: + order: 111 +--- + +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 ` ()`. + + + 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(.data = 3.0, .times = 1); + + // 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(.data = 3.5, .times = 10); + + // Unnamed and named + test_named_default(3, .dummy = false); + + // Overwriting an unnamed argument with named 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: + +1. single typed +2. explicitly typed any: pass non-any arguments as references +3. implicitly typed any: arguments are implicitly converted to references (use with care) +4. 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 + } + +### Functions and optional returns + +The return parameter may be an *optional result type* – a type suffixed by `!` indicating that this +function might either return a regular value or an *optional result value*. + +The below example might return optional values from both the `SomeError` optional enum as well as +the `OtherResult` type. + + fn double! testError() + { + 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 result type* arguments will only execute +if all optional values contain *expected results*, otherwise *the first* *optional result value* is returned. + + fn void test() + { + // The following line is either prints a value less than 0.2 + // or does not print at all: + io::printf("%d\n", testError()); + + double x = (testError() + testError()) else 100; + + // This prints either a value less than 0.4 or 100: + io::printf("%d\n", x); + } + +This allows us to chain functions: + + fn void printInputWithExplicitChecks() + { + string! line = readLine(); + if (try line) + { + // line is a regular "string" here. + int! val = atoi(line); + if (try val) + { + io::printf("You typed the number %d\n", val); + return; + } + } + io::printf("You didn't type an integer :(\n"); + } + + fn void printInputWithChaining() + { + if (try int val = atoi(readLine())) + { + io::printf("You typed the number %d\n", val); + return; + } + io::printf("You didn't type an integer :(\n"); + } + +## 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.mayOpen(State state) + { + switch (state) + { + case STOPPED: return true; + case RUNNING: return false; + } + } + +### Implicit first parameters + +Because the type of the first method 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 testFoo(int foo) + { + return foo * 10; + } + +Will in debug builds be compiled into something like this: + + fn int testFoo(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 testExample(int bar) + { + if (testFoo(bar) == 0) return -1; + return 1; + } + +May be optimized to: + + fn int testExample(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 not 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 an 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 "); + } + diff --git a/src/content/docs/references/docs/generics.md b/src/content/docs/references/docs/generics.md new file mode 100644 index 0000000..cd18c62 --- /dev/null +++ b/src/content/docs/references/docs/generics.md @@ -0,0 +1,71 @@ +--- +title: Generics +description: Generics +sidebar: + order: 118 +--- + +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(); +``` + +Code inside a generic module may use the generic parameters as if they were well-defined symbols: + +``` +module foo_test(); + +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(); +def test_float = foo_test::test(); + +... + +FooFloat f; +Foo() g; + +... + +test_float(1.0, &f); +foo_test::test()(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(); + +/* .. code * ../ +``` + +``` +def testFunction = vector::testFunc(); + +// This would give the error +// --> Parameter(s) failed validation: +// @require "$assignable((TypeB)1, TypeA) && $assignable((TypeC)1, TypeA)" violated. +``` + diff --git a/src/content/docs/references/docs/libraries.md b/src/content/docs/references/docs/libraries.md new file mode 100644 index 0000000..8367499 --- /dev/null +++ b/src/content/docs/references/docs/libraries.md @@ -0,0 +1,70 @@ +--- +title: Libraries +description: Libraries +sidebar: + order: 127 +--- + +*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: + +```json +{ + "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* + diff --git a/src/content/docs/references/docs/macros.md b/src/content/docs/references/docs/macros.md new file mode 100644 index 0000000..a6d5040 --- /dev/null +++ b/src/content/docs/references/docs/macros.md @@ -0,0 +1,407 @@ +--- +title: Macros +description: Macros +sidebar: + order: 119 +--- +The macro capabilities of C3 reaches across several constructs: macros (prefixed with `@` at invocation), [generic functions](../generics/#generic-functions), [generic modules](../generics/#generic-modules), 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 + #if defined(x) && Y > 3 + int z; + #endif + + // C3 + $if $defined(x) && $y > 3: + int z; + $endif + + // or + int z @if($defined(x) && $y > 3); + + + +### Macros + + // C + #define M(x) ((x) + 2) + #define UInt32 unsigned int + + // Use: + int y = M(foo() + 2); + UInt32 b = y; + + // C3 + macro m(x) + { + return x + 2; + } + def UInt32 = uint; + + // Use: + int y = @m(foo() + 2); + UInt32 b = y; + +### Dynamic scoping + + // C + #define Z() ptr->x->y->z + int x = Z(); + + // C3 + ... currently no corresponding functionality ... + + +### Reference arguments + +Use `&` in front of a parameter to capture the variable and pass it by reference without having to explicitly use `&` and pass a pointer. +(Note that in C++ this is allowed for normal functions, whereas for C3 it is only permitted with macros. Also, +in C3 the captured argument isn't automatically dereferenced) + + // C + #define M(x, y) x = 2 * (y); + ... + M(x, 3); + + // C3 + macro m(&x, y) + { + *x = 2 * y; + } + ... + m(x, 3); + + +### First class types + + // C + #define SIZE(T) (sizeof(T) + sizeof(int)) + + // C3 + macro size($Type) + { + return $Type.sizeof + int.sizeof; + } + +### Trailing blocks for macros + + // C + #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 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 + #define offsetof(T, field) (size_t)(&((T*)0)->field) + + // C3 + macro usz offset($Type, #field) + { + $Type* t = null; + return (usz)(uptr)&t.#field; + } + +### Declaration attributes + + + // C + #define PURE_INLINE __attribute__((pure)) __attribute__((always_inline)) + int foo(int x) PURE_INLINE { ... } + + // C3 + define @PureInline = { @pure @inline }; + fn int foo(int) @PureInline { ... } + + +### Declaration macros + + // C + #define DECLARE_LIST(name) List name = { .head = NULL }; + // Use: + DECLARE_LIST(hello) + + // C3 + ... currently no corresponding functionality ... + +### Stringification + + #define CHECK(x) do { if (!x) abort(#x); } while(0) + + // C3 + macro fn 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 ()`. All user defined macros use the @ symbol if they use the `&` or `#` parameters. + +The parameters have different sigils: `$` means compile time evaluated (constant expression or type). `#` indicates an expression that is not yet evaluated, but is bound to where it was defined. Finally `&` is used to *implicitly* pass a parameter by reference. +`@` is required on macros that use `#` and `&` parameters. + +A basic swap: + + /** + * @checked $assignable(*a, $typeof(*b)) && $assignable(*b, $typeof(*a)) + */ + macro void @swap(&a, &b) + { + $typeof(*a) 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) + { + $typeof(a) 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](../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` + +### $varef + +Returns the argument as an lvalue. This corresponds to `&myref` style parameters, +e.g. `*$varef(1) = 123`. + +### $vasplat + +`$vasplat` allows you to paste the varargs in the call into another call. For example, +if the macro was called with values `"foo"` and `1`, the code `foo($vasplat())`, would become `foo("foo", 1)`. +You can even extract provide a range as the argument: `$vasplat(2..4)` (in this case, this would past in +arguments 2, 3 and 4). + +Nor is it limited to function arguments, you can also use it with initializers: + + 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 + diff --git a/src/content/docs/references/docs/modules.md b/src/content/docs/references/docs/modules.md new file mode 100644 index 0000000..35e7749 --- /dev/null +++ b/src/content/docs/references/docs/modules.md @@ -0,0 +1,449 @@ +--- +title: Modules +description: Modules +sidebar: + order: 106 +--- + + +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 module `bar` of the module `foo`. +- 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: + +1. The `std::core` module (and sub modules). +2. Any other module sharing the same top module. E.g. the module `foo::abc` will implicitly also import modules `foo` and `foo::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 @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() + { + a::a_function(); // <-- error, a_function is private + b::b_function(); // Allowed since import converted it to "public" in this context. + } + + +*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: + +1. Functions, macros, constants and variables require *at least* the (sub-) module name. +2. Types do not require the module name unless the name is ambiguous. +3. 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)) + { + dynlib::do_another_thing(); // Error: This function is not available with 3.0 + } + +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: +```c +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`. + +```c +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() +{ + ... +} +``` diff --git a/src/content/docs/references/docs/naming.md b/src/content/docs/references/docs/naming.md new file mode 100644 index 0000000..37eaf17 --- /dev/null +++ b/src/content/docs/references/docs/naming.md @@ -0,0 +1,108 @@ +--- +title: Naming Rules +description: Naming Rules +sidebar: + order: 129 +--- + +C3 introduces fairly strict naming rules to reduce ambiguity. + +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); +} +``` \ No newline at end of file diff --git a/src/content/docs/references/docs/operators.md b/src/content/docs/references/docs/operators.md new file mode 100644 index 0000000..225811b --- /dev/null +++ b/src/content/docs/references/docs/operators.md @@ -0,0 +1,81 @@ +--- +title: Operator Overloading +description: Operator Overloading +sidebar: + order: 121 +--- + +C3 allows some limited operator overloading for working with containers. + +## "Element at" operator [] + +Implementing `[]` allows a type to use the `my_type[]` 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[]`, which may +be retrieved in a different way. If this overload isn't defined, then `&my_type[]` 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[] = `. + + fn void Foo.get_ref(&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 `[]` and `len` need to +be implemented. 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); + } + } \ No newline at end of file diff --git a/src/content/docs/references/docs/optionals.md b/src/content/docs/references/docs/optionals.md new file mode 100644 index 0000000..42e2178 --- /dev/null +++ b/src/content/docs/references/docs/optionals.md @@ -0,0 +1,575 @@ +--- +title: Optionals and Error Handling +description: Optionals and Error Handling +sidebar: + order: 115 +--- + +Optionals in C3 work differently from other languages: + +1. It is similar to a "Result" type in capabilities. +2. It is not quite a type: it can be used for variable and return values - but not for parameters or struct member types. +3. It "cascades" the optional-ness of an expression, somewhat reminiscent of the "flatmap" operation. +4. A function called with an Optional is only invoked if it has an actual value. + +## What is an Optional? + +In C3 it is either: + +1. A variable that works as a union between a `fault` value and an actual value, we call this latter the "result". +2. A return value that is either a `fault` or a result. + +We can construct an Optional by adding the `!` suffix to a type: + +```c +int! a; // An optional value with the result type of "int" + +fn int! foo() // A function returning an optional value with the result type of "int" +{ + ... +} +``` + +It is not possible to create an Optional of an Optional (so for example `int!!` is never valid). + +*Using* a variable or return with an Optional type yields an optional: + + int! a = ... + fn int! foo() { ... } + + double! x = a * 3.14159; + double! y = foo() * 0.3333333333; + double! z = x * y; + +Similar to basic operations it's possible to use an Optional value as a call parameter. The return value then becomes Optional + + int! a = ... + fn double bar(int x) { ... } + + // "bar" can be called because the result type of the Optional is "int" + // but the return value will become Optional: + double! x = bar(a); + +## Optional execution + +The most important feature of Optionals is how it will only optionally execute +operations. Given a call with the arguments a0, a1, ... the call will only +be invoked if all of the arguments evaluate to real values. + + int! a = 1; + int! b = MyResult.FOO?; + + // "baz" called, because "a" is has a result. + int! z = baz(a, a); // same as z = bar(1, 1) + + // "baz" is not called, because "b" evaluates to a fault + int! y = baz(a, b); + + // Due to evaluation ordering "abc" is called, but not "def" nor "baz": + int! x = baz(abc(a), def(b)); + + // Due to evaluation ordering none of "abc", "def" or "baz" is called: + int! x2 = baz(def(b), abc(a)); + +We can think of the above example `int! x = baz(a, b)` as the following: + +1. Evaluate the first argument. +2. If it is a `fault` then we're done, set `x` to this fault. +3. Evaluate the second argument. +4. If it is a `fault` then we're done, set `x` to this fault. +5. Execute `baz` with the result values from the arguments. + +Optional execution allows us to avoid dealing with intermediary errors, we can simply +collect them together: + +`int! x = foo_return_optional(other_return_optional(optional_value))` + +## Optional unwrapping + +It's not possible to assign an Optional to a non-optional type: + +```c +int! a = ... + +int b = a; // <- ERROR, can't assign "int!" to "int" +``` + +To assign it we have two options, `if-try` and implicit unwrap. + +### If-try + +If-try tests an Optional and executes the "then" part if the value is a result. + + int! a = ...; + int b; + + if (try int x = a) + { + // This part only executes if "a" has a result. + b = x; + } + +There are abbreviated variants of `if-try`: + + if (try x = a) { ... } // Infer type of "x" + if (try a) { ... } // Unwrap "a" inside of this context. + +It is possible to add conditions to an `if-try` but they must be joined with `&&` +"or" (i.e. `||`) is not allowed: + + if (try a && try z && a > z) + { + // Only executes if a and z have results + // *and* a > z + ... + } + + // if (try a || try z) { ... } <- ERROR! + + +### If-catch + +If-catch works the other way and only executes if the Optional is a fault: + + if (catch anyfault f = a) + { + // Handle the fault + } + +Just like for if-try there are abbreviated variants: + + if (catch f = a) { ... } // "f" has the type of "anyfault" + if (catch a) { ... } // Discards the actual fault value + +It is possible to catch multiple errors by grouping them with `,`: + + if (catch f = a, b, foo()) + { + // Returns the fault from a, b or foo() + // trying each in order. + // foo() is only called if neight a nor b has a fault. + } + +### Implicit unwrapping with if-catch. + +If an `if-catch` returns or jumps out of the current scope in some way, then +the variable becomes implicit unwrapped to its result type in that scope: + + int! a = foo_may_error(); + + if (catch a) + { + return; + } + + // a is now considered a plain int: + int b = a; + +### Getting the fault without unwrapping + +If-catch is not necessary in order to get the underlying fault from any Optional. Instead the macro `@catch` +may be used. + + int! a = ... + + anyfault f = @catch(a); + + if (!f) + { + // No error! + } + +### If-catch switching + +If-catch can also immediately switch on the fault value: + + if (catch a) + { + case MyResult.FOO: + ... + case IoError.NO_SUCH_FILE: + ... + case IoError.FILE_NOT_DIR: + ... + default: + ... + } + +The above being equivalent to: + + if (catch f = a) + { + switch (f) + { + case MyResult.FOO: + ... + case IoError.NO_SUCH_FILE: + ... + case IoError.FILE_NOT_DIR: + ... + default: + ... + } + } + + +### Testing for a result without unwrapping + +The `@ok` macro will return `true` is an Optional is a result and `false` +it is a fault. Functionally it is equivalent to `!@catch` + + int! a = ... + + bool was_ok = @ok(a); + assert(was_ok == !@catch(a)); + +## `fault` and `anyfault` + +Faults are defined similar to simple enums: + + fault MyResult + { + SOMETHING_HAPPENED, + UNEXPECTED_ERROR, + } + +The union of all of such types is `anyfault`: + + MyResult foo = MyResult.UNEXPECTED_ERROR; + + anyfault x = foo; + x = IoError.NO_SUCH_FILE; // Also ok + +## Setting the `result` and the `fault` + +To set the `result` of an Optional, use regular assignment, and +to set the `fault` `?` suffix operator. + + int! a = 1; + int! b = MyResult.UNEXPECTED_ERROR?; // <- '?' sets the fault + + MyResult foo = MyResult.UNEXPECTED_ERROR; + anyfault bar = IoError.NO_SUCH_FILE; + + int! c = foo?; // c has the fault MyResult.UNEXPECTED_ERROR + int! d = bar?; // d has the fault IoError.NO_SUCH_FILE? + +## Rethrow, or-else and force unwrap + +Three helper operators are provided for working with Optionals: +rethrow `!`, or-else `??` and force unwrap `!!`. + +### Rethrow + +Sometimes the optional fault needs to be propagated upwards, here is +an example: + + int! a = foo_may_error(); + + if (catch f = a) + { + return f?; // Pass the fault upwards. + } + +To simplify this the rethrow operator `!` can be used: + + // Equivalent to the code above. + int! a = foo_may_error()!; + +Because the rethrow operator automatically returns on a fault, the return value +turns into its result. In the above example the type of `foo_may_error()!` becomes `int`: + + int b = foo_may_error()!; // This works + +### Or-else + +Sometimes we have this situation: + + int! a_temp = foo_may_error(); + int a; + if (try a_temp) + { + a = a_temp; + } + else + { + a = -1; + } + +The or-else operator `??` works similar to `?:` allowing you to do this in a single expression: + + // Equivalent to the above + int a = foo_may_error() ?? -1; + +### Force unwrap + +Sometimes a `fault` is completely unexpected, and we want to assert if +it happens: + + int! a = foo_may_error(); + if (catch f = a) + { + unreachable("Unexpected fault %s", f); + } + ... use "a" as int here ... + +The force unwrap operator `!!` allows us to express this similar to rethrow and or-else: + + int a = foo_may_error()!!; + +## No void! variables + +The `void!` type has no possible representation as a variable, and may +only be a return type. To store the result of a `void!` function, +one can use the `@catch` macro to convert the result to +an `anyfault`: + + fn void! test() { ... } + + anyfault f = @catch(test()); + +## Examples + +#### Basic usage with immediate error handling + + // Open a file, we will get an optional result: + // Either a File* or an error. + File*! file = file::open("foo.txt"); + + // We can extract the optional result value using "catch" + if (catch f = file) + { + // Might print "Error was FILE_NOT_FOUND" + io::printfn("Error was %s", f.name()); + + // Might print "Error code: 931938210" + io::printfn("Error code: %d", (uptr)err); + return; + } + + // Because of implicit unwrapping, the type of + // `file` is File* here. + +We can also execute just in case of success: + + File*! file2 = file::open("bar.txt"); + + // Only true if there is an expected result. + if (try file2) + { + // Inside here file2 is a regular File* + } + +#### Composability of calls + + fn int! foo_may_error() { ... } + fn int mult(int i) { ... } + fn int! save(int i) { ... } + + fn void test() + ( + // "mult" is only called if "fooMayError()" + // returns a non optional result. + int! j = mult(foo_may_error()); + + int! k = save(mult(foo_may_error())); + if (catch f = k) + { + // The fault may be from foo_may_error + // or save! + } + ) + +#### Returning a fault + +Returning a fault looks like a normal return but with the `?` + +``` +fn void! find_file() +{ + if (file::exists("foo.txt")) return IoError.FILE_NOT_FOUND?; + /* ... */ +} +``` + +#### Calling a function automatically returning any optional result + +The `!` suffix will create an implicit return on a fault. + +``` +fn void! find_file_and_test() +{ + find_file()!; + // Implictly: + // if (catch f = find_file()) return f?; +} +``` + +#### Force unwrapping to panic on fault + +The `!!` will issue a panic if there is a fault. + +``` +fn void find_file_and_test() +{ + find_file()!!; + // Implictly: + // if (catch find_file()) unreachable("Unexpected error"); +} +``` + +#### Catching faults to implicitly unwrap + +Catching faults and then exiting the scope will implicitly unwrap the +variable: + + fn void find_file_and_no_fault() + { + File*! res = find_file(); + if (catch res) + { + io::printn("An error occurred!"); + // Jump required for unwrapping! + return; + } + // res is implicitly unwrapped here. + // and have an effective type of File*. + } + + +#### Only run if there is no fault + +``` +fn void do_something_to_file() +{ + void! res = find_file(); + if (try res) + { + io::printn("I found the file"); + } +} +``` + +#### Catching and switch on fault + + fn void! find_file_and_parse2() + { + if (catch f = find_file_and_parse()) + { + case IOError.FILE_NOT_FOUND: + io::printn("Error loading the file!"); + default: + return f?; + } + } + + +#### Default values using or-else + + + fn int get_int() + { + return get_int_number_or_fail() ?? -1; + } + + +#### Get the fault from an optional without `if-catch` + + fn void test_catch() + { + int! i = get_something(); + anyfault maybe_fault = @catch(i); + if (maybe_fault) + { + // Do something with the fault + } + } + +#### Test if something has a value without `if-try` + + fn void test_something() + { + int! i = try_it(); + bool worked = @ok(i); + if (worked) + { + io::printn("Horray! It worked."); + } + } + +### Some common techniques + +Here follows some common techniques using optional values. + +#### Catch and return another error + +In this case we don't want to return the underlying fault, but instead return out own replacement error. + + fn void! return_own() + { + int! i = try_something() ?? OurError.SOMETHING_FAILED?; + .. do things .. + } + + fn void! return_own_rethrow() + { + int i = try_something() ?? OurError.SOMETHING_FAILED?!; // Cause immediate rethrow + .. do things .. + } + +#### Using void! as a boolean + +A common pattern in C is to use a boolean result to indicate success. `void!` can be used +in a similar way: + + // C + bool store_foo(Foo* f) + { + if (!foo_repository_is_valid()) return false; + return foo_repo_store_foo(f); + } + + void test() + { + Foo* f = foo_create(); + if (store_foo(f)) + { + puts("Storage worked"); + return; + } + ... + } + + + // C3 + fn void! store_foo(Foo* f) + { + if (!foo_repository_is_valid()) return FooFaults.INVALID_REPO?; + return foo_repo_store_foo(f); + } + + fn void test() + { + Foo* f = foo_create(); + if (@ok(store_foo(f))) + { + io::printn("Storage worked"); + return; + } + ... + } + +## Interfacing with C + +For C the interface to C3, the fault is returned as the regular return while the result +is passed by reference: + +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. diff --git a/src/content/docs/references/docs/precedence.md b/src/content/docs/references/docs/precedence.md new file mode 100644 index 0000000..38bda3b --- /dev/null +++ b/src/content/docs/references/docs/precedence.md @@ -0,0 +1,56 @@ +--- +title: Precedence +description: Precedence +sidebar: + order: 125 +--- + +Precedence rules in C3 differs from C/C++. Here are all precedence levels in C3, listed from highest (1) to lowest (11): + +1. `()`, `[]`, `.`, `!!` postfix `!`, `++` and `--` +2. `@`, prefix `-`, `~`, prefix `*`, `&`, prefix `++` and `--` +3. infix `*`, `/`, `%`, `*%` +4. `<<`, `>>` +5. `^`, `|`, infix `&` +6. `+`, infix `-` +7. `==`, `!=`, `>=`, `<=`, `>`, `<` +8. `&&` +9. `||` +10. ternary `?:` `??` +11. `=`, `*=`, `/=`, `%=`, `+=`, `-=`, `<<=`, `>>=`, `&=`, `^=`, `|=` + + +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. \ No newline at end of file diff --git a/src/content/docs/references/docs/reflection.md b/src/content/docs/references/docs/reflection.md new file mode 100644 index 0000000..9e0b69f --- /dev/null +++ b/src/content/docs/references/docs/reflection.md @@ -0,0 +1,334 @@ +--- +title: Reflection +description: Reflection +sidebar: + order: 120 +--- + +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` +- `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 an array containing the fields in a bitstruct, struct or union. The +elements have the *compile time only* type of `member_ref`, + + 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 + +#### names + +Returns a subarray containing the names of an enum or fault. + + enum FooEnum + { + BAR, + BAZ + } + String[] x = FooEnum.names; // ["BAR", "BAZ"] + +#### params + +*Only available for function types.* +Returns a list typeid for all parameters. + + def TestFunc = fn int(int, double); + String s = TestFunc.params[1].nameof; // "double" + +#### 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 subarray 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` +- `$checks` +- `$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; diff --git a/src/content/docs/references/docs/sample.md b/src/content/docs/references/docs/sample.md new file mode 100644 index 0000000..0779d62 --- /dev/null +++ b/src/content/docs/references/docs/sample.md @@ -0,0 +1,290 @@ +--- +title: More Code Examples +description: More Code Examples +sidebar: + order: 135 +--- + +Here is a bit of code manually converted to C3 from C. + +``` +const uint OFFSET = 8; +const uint BIN_COUNT = 9; +const uint BIN_MAX_IDX = BIN_COUNT - 1; +const uint OVERHEAD = Footer.sizeof + Node.sizeof; +const usz MIN_WILDERNESS = 0x2000; +const usz MAX_WILDERNESS = 0x1000000; +const usz HEAP_INIT_SIZE = 0x10000; +const usz HEAP_MAX_SIZE = 0xF0000; +const usz HEAP_MIN_SIZE = 0x10000; +const uint MIN_ALLOC_SZ = 4; + +struct Node +{ + uint hole; + uint size; + Node* next; + Node* prev; +} + +struct Footer +{ + Node *header; +} + +struct Bin +{ + Node* head; +} + +struct Heap +{ + uptr start; + uptr end; + Bin*[BIN_COUNT] bins; +} + + +/** + * @require heap != null, start > 0 + */ +fn void Heap.init(Heap* heap, uptr start) +{ + Node* init_region = (Node*)start; + init_region.hole = 1; + init_region.size = HEAP_INIT_SIZE - Node.sizeof - Footer.sizeof; + + init_region.createFoot(); + + heap.bins[get_bin_index(init_region.size)].addNode(init_region); + + heap.start = start; + heap.end = start + HEAP_INIT_SIZE; +} + +fn void* Heap.alloc(Heap* heap, uint size) +{ + uint index = get_bin_index(size); + Bin* temp = (Bin*)heap.bins[index]; + Node* found = temp.getBestFit(size); + + while (!found) + { + temp = heap.bins[++index]; + found = temp.getBestFit(size); + } + + if ((found.size - size) > (OVERHEAD + MIN_ALLOC_SZ)) + { + Node* split = (Node*)((char*)found + Node.sizeof + Footer.sizeof) + size; + split.size = found.size - size - (uint)Node.sizeof - (uint)Footer.sizeof; + split.hole = 1; + + split.createFoot(); + + uint new_idx = get_bin_index(split.size); + + heap.bins[new_idx].addNode(split); + + found.size = size; + found.createFoot(); + } + + found.hole = 0; + heap.bins[index].removeNode(found); + + Node* wild = heap.getWilderness(); + if (wild.size < MIN_WILDERNESS) + { + if (!heap.expand(0x1000)) return null; + } + else if (wild.size > MAX_WILDERNESS) + { + heap.contract(0x1000); + } + + found.prev = null; + found.next = null; + return &found.next; +} + +/** + * @require p != null + */ +fn void Heap.free(Heap* heap, void *p) +{ + Bin* list; + Footer* new_foot, old_foot; + + Node* head = (Node*)((char*)p - OFFSET); + if (head == (Node*)((uptr)heap.start)) + { + head.hole = 1; + heap.bins[get_bin_index(head.size)].addNode(head); + return; + } + + Node* next = (Node*)((char*)head.getFoot() + Footer.sizeof); + Footer* f = (Footer*)((char*)(head) - Footer.sizeof); + Node* prev = f.header; + + if (prev.hole) + { + list = heap.bins[get_bin_index(prev.size)]; + list.removeNode(prev); + + prev.size += OVERHEAD + head.size; + new_foot = head.getFoot(); + new_foot.header = prev; + + head = prev; + } + + if (next.hole) + { + list = heap.bins[get_bin_index(next.size)]; + list.removeNode(next); + + head.size += OVERHEAD + next.size; + + old_foot = next.getFoot(); + old_foot.header = null; + next.size = 0; + next.hole = 0; + + new_foot = head.getFoot(); + new_foot.header = head; + } + + head.hole = 1; + heap.bins[get_bin_index(head.size)].addNode(head); +} + +fn uint Heap.expand(Heap* heap, usz sz) +{ + return 0; +} + +fn void Heap.contract(Heap* heap, usz sz) +{ + return; +} + +fn uint get_bin_index(usz sz) +{ + uint index = 0; + sz = sz < 4 ? 4 : sz; + + while (sz >>= 1) index++; + index -= 2; + + if (index > BIN_MAX_IDX) index = BIN_MAX_IDX; + return index; +} + +fn void Node.createFoot(Node* head) +{ + Footer* foot = head.getFoot(); + foot.header = head; +} + +fn Footer* Node.getFoot(Node* node) +{ + return (Footer*)((char*)node + Node.sizeof + node.size); +} + +fn Node* Heap.getWilderness(Heap* heap) +{ + Footer* wild_foot = (Footer*)((char*)heap.end - Footer.sizeof); + return wild_foot.header; +} + +fn void Bin.removeNode(Bin* bin, Node* node) +{ + if (!bin.head) return; + if (bin.head == node) + { + bin.head = bin.head.next; + return; + } + + Node* temp = bin.head.next; + while (temp) + { + if (temp == node) + { + if (!temp.next) + { + temp.prev.next = null; + } + else + { + temp.prev.next = temp.next; + temp.next.prev = temp.prev; + } + return; + } + temp = temp.next; + } +} + +fn void Bin.addNode(Bin* bin, Node* node) +{ + node.next = null; + node.prev = null; + + Node* temp = bin.head; + + if (!bin.head) + { + bin.head = node; + return; + } + + Node* current = bin.head; + Node* previous = null; + + while (current != null && current.size <= node.size) + { + previous = current; + current = current.next; + } + + if (!current) + { + previous.next = node; + node.prev = previous; + } + else + { + if (previous) + { + node.next = current; + previous.next = node; + + node.prev = previous; + current.prev = node; + } + else + { + node.next = bin.head; + bin.head.prev = node; + bin.head = node; + } + } +} + +fn Node* Bin.getBestFit(Bin* bin, usz size) +{ + if (!bin.head) return null; + + Node* temp = bin.head; + + while (temp) + { + if (temp.size >= size) return temp; + temp = temp.next; + } + return null; +} +``` \ No newline at end of file diff --git a/src/content/docs/references/docs/specification.md b/src/content/docs/references/docs/specification.md new file mode 100644 index 0000000..95e7591 --- /dev/null +++ b/src/content/docs/references/docs/specification.md @@ -0,0 +1,1631 @@ +--- +title: C3 Specification +description: C3 Specification +sidebar: + order: 140 +--- + +*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 non-doc 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 non-doc 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 as `a`, `--`, `b`, which does not form a grammatically correct expression, even though the tokenization `a`, `-`, `-`, `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: + +1. White space +2. Comment +3. Doc Comment +4. Token + +A token may be: + +1. Identifier +2. Keyword +3. Literal +4. Separator +5. Operator + +A Doc Comment consists of: + +1. A stream of descriptive text +2. 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: + +1. `// text` a line comment. The text between `//` and line end is ignored. +2. `/* 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 comments + +1. `/** 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 "_"* 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: + +```text +\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: + +```c +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](#compile-time-string-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: + +```text +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: + +```text +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: + +```c +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. + +### Subarray types + +The subarray consist of a pointer, followed by an usz length, having the alignment of pointers. + +### Pointer types + +A pointer is the address to memory. + +```text +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](#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](#align-attribute) for +details on changing the alignment. + +### Fault types + +A fault is an extensible enum which can be used to create an [optional](#optional-types). + +#### Alignment + +A fault type has the same alignment as a pointer. See [align attribute](#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](#typeid-type). +It cannot be dereferenced. + +#### Fields + +`.ptr` returns a `void*` pointer to the underlying value `.type` returns the [typeid](#typeid-type) +of the underlying value. + +#### Switching over `any` + +Switching over an `any` value creates an [any switch](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 ?` 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 +``` + +#### 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, subarray or vararray. 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 + +#### 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: + +```c +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: + +```c +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): + +1. 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. +2. 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](#typeid-type), and the cases follows the rules +for [switching over typeid](#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: + +```c +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 subarray, 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, 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 | +| @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 | + +### 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. + +## 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. \ No newline at end of file diff --git a/src/content/docs/references/docs/standard_library.md b/src/content/docs/references/docs/standard_library.md new file mode 100644 index 0000000..9129beb --- /dev/null +++ b/src/content/docs/references/docs/standard_library.md @@ -0,0 +1,428 @@ +--- +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`. + +```c +int a = 3; +int b = 5; +@swap(a, b); +io::printfn("%d", a); // Prints 5 +``` + +### varcast(any* v, $Type) + +Optionally cast the value `v` to type `$Type*` on failure returns `VarCastResult.TYPE_MISMATCH`. + +```c +int b; +any* a = &b; +float*! c = varcast(a, float); // Will return TYPE_MISMATCH +int*! d = varcast(a, int); // Works! +``` + +### void unreachable($string = "Unreachable statement reached.") + +Mark a code path as unreachable. + +```c +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. +```c +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. +```c +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. +- `AnyCastResult` when a any cast 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. + +These calls takes an optional `using` parameter, replacing the default +allocator with a custom one. + +```c +char* data = malloc(8); +char*! data2 = malloc_checked(8); +int[<16>]*! data3 = malloc_aligned(16 * int.sizeof), 128); +char* data2 = malloc(8, .using = my_allocator); +``` + +### new($Type), new_array($Type, usz elements) + +The first form allocates a single element of $Type, returning the pointer, +the second form allocates a slice with `elements` number of elements, returning +a subarray of the given length. Elements are not initialized. + +```c +int* int = malloc(int); +int[] ints = new_array(int, 100); // Allocated int[100] on the heap. +``` + +```c +struct Abc +{ + int header; + char[*] data; +} + +... + +// Allocate a "Type" but add "data_len" bytes +// for the flexible array member "data": +Type* t = new(Abc, .end_padding = data_len); +``` + + +### 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. + +```c +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`. + +### void @pool(;@body) + +Opens a temporary memory scope. + +```c +@poo() +{ + // This allocation uses the dynamic arena + Foo* f = talloc(Foo); +}; + +``` + + +### @volatile_load(&x) + +Returns the value in `x` using a volatile load. + +```c +// 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. + +```c +// 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. + +```c + +Foo* f = talloc(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. + +```c +Foo f = ... + +return @clone(f); + +``` + +### @tclone(&value) + +Same as `@clone` but uses the temporary allocator. + +## std::core::types + +### bool is_comparable($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. + +```c +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 + +### usz! printf(String format, args...) @maydiscard +Regular printf functionality: `%s`, `%x`, `%d`, `%f` and `%p` are supported. +Will also print enums and vectors. + +### usz! DString.appendf(DString* str, String format, args...) @maydiscard +Same as printf but on dynamic strings. + +### usz! File.printf(File file, String format, args...) @maydiscard +Same as printf but on files. + +### void! File.open(File* file, String filename, String mode) +Open a file with the given file name with the given mode (r, w etc) + +### void! File.seek(File *file, long offset, Seek seekMode = Seek.SET) +Seek in a file. Based on the libc function. + +### void! File.close(File *file) @inline +Close a file, based on the libc function. + +### bool File.eof(File* file) @inline +True if EOF has been reached. Based on the libc function. + +### void! File.putc(File *file, char c) +Write a single byte to a file. See the libc function. + +### usz File.read(File* file, void* buffer, usz items, usz element_size = 1) +Read into a buffer, based on the libc function. + +### usz File.write(File* file, void* buffer, usz items, usz element_size = 1) +Write to a buffer, based on the libc function. + +### stdout(), stdin(), stderr() +Return stdout, stdin and stderr respectively. + +## std::collections::list() + +Generic list module, elements are of `Type`. + +```c +import std::collections::list; +def MyIntList = List(); + +... + +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. + diff --git a/src/content/docs/references/docs/statements.md b/src/content/docs/references/docs/statements.md new file mode 100644 index 0000000..e43652f --- /dev/null +++ b/src/content/docs/references/docs/statements.md @@ -0,0 +1,154 @@ +--- +title: Statements +description: Statements +sidebar: + order: 112 +--- + +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 (c < 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(); + } \ No newline at end of file diff --git a/src/content/docs/references/docs/syntax.md b/src/content/docs/references/docs/syntax.md new file mode 100644 index 0000000..60ff1cb --- /dev/null +++ b/src/content/docs/references/docs/syntax.md @@ -0,0 +1,1321 @@ +--- +title: Grammar +description: Grammar +sidebar: + order: 134 +--- + +## 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 $checks +$default $defined $echo $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 + +```c +%{ + +#include +#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; +} +``` \ No newline at end of file diff --git a/src/content/docs/references/docs/types.md b/src/content/docs/references/docs/types.md new file mode 100644 index 0000000..8287933 --- /dev/null +++ b/src/content/docs/references/docs/types.md @@ -0,0 +1,637 @@ +--- +title: Types +description: Types +sidebar: + order: 107 +--- + +As usual, types are divided into basic types and user defined types (enum, union, struct, faults, aliases). 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(const char* pathname, Stat* buf); +``` + +This would for example affect 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. +In order to signal restrictions on parameter usage, parameter [preconditions](../preconditions/) are used. +`typedef` has a slightly different syntax and renamed `def`. + +C3 also requires all function pointers to be used with an alias, so: + + 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](https://en.wikipedia.org/wiki/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 endianess. + +##### 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''`. Hex encoded values work as base64 but with the format `x''`. 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 | +| ------------ | --------:| +| float16* | 16 | +| float | 32 | +| double | 64 | +| float128* | 128 | + +*support depends on platform + +##### 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 | +| ------------ | --------:| +| 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 | +| CFloat | float | +| CDouble | double | +| CLongDouble | long double | + + +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 `.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. +It is possible to cast the any pointer to any pointer type, which will return `null` if the types don't match, +or the pointer value otherwise. + + int x; + any* y = &x; + double *z = (double*)y; // Returns null + int* w = (int*)x; // Returns the pointer to x + +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 + } + } + +`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]`. Subarrays use the `type[]`. For initialization the wildcard `type[*]` can be used to infer the size +from the initializer. See the chapter on [arrays](../arrays). + +### Vector types + +Vectors use `[]` 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 = ` + + 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 = ` + + 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; } + + // Using def - usually recommended: + def IntMyList = MyList(); + IntMyList abc; + + // Inline type definition + MyList bcd = MyList(); + +Read more about generic types on [the page about generics](../generics). + +## Enum + +Enum (enumerated) types use the following syntax: + + enum State : int + { + PENDING, + RUNNING, + TERMINATED + } + +Enum constants are namespaces by default, just like C++'s class enums. So accessing the enums above would for example use `State.PENDING` rather than `PENDING`. + +### Enum type inference + +When an enum is used in where the type can be inferred, like in case-clauses or in variable assignment, it is allowed to drop the enum name: + + State foo = PENDING; // State.PENDING is inferred. + switch (foo) + { + case RUNNING: // State.RUNNING is inferred + ... + default: + ... + } + + fn void test(State s) { ... } + + ... + + test(RUNNING); // State.RUNNING is inferred + + +In the case that it collides with a global in the same scope, it needs the qualifier: + + module test; + + fn void testState(State s) { ... } + + const State RUNNING = State.TERMINATED; // Don't do this! + + ... + + test(RUNNING); // Ambiguous + test(test::RUNNING); // Uses global. + test(State.RUNNING); // Uses enum constant. + + +### Enum associated values + +It is possible to associate each enum value with a static value. + + enum State : int (String state_desc, bool active) + { + PENDING("pending start", false), + RUNNING("running", true), + TERMINATED("ended", false) + } + + ... + + State s = get_state(); + + io::printfn("Currently the process is %s", s.state_desc); + if (s.active) do_something(); + +## Faults + +`fault` defines a set of optional result values, that are similar to enums, but are used for +optional returns. + + fault IOResult + { + IO_ERROR, + PARSE_ERROR + } + + fault MapResult + { + NOT_FOUND + } + +Like the typeid, the constants are pointer sized and each value is globally unique, even when +compared to other faults. 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. + +A fault may be stored as a normal value, but is also unique in that it may be passed +as the optional result value using the `!` suffix operator. + + +## Optional Result Types + +An *optional result type* is created by taking a type and appending `!`. +An optional result type is a tagged union containing either the *expected result* or *an optional result value* +(which is a fault). + + int! i; + i = 5; // Assigning a real value to i. + i = IOResult.IO_ERROR?; // Assigning an optional result to i. + + +Only variables and return variables may be optionals. Function and macro parameters may not be optionals. + + fn Foo*! getFoo() { ... } // Ok! + fn void processFoo(Foo*! f) { ... } // Error + int! x = 0; // Ok! + + +Read more about the optional types on the page about [optionals and error handling](../optionals). + + +## 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. + + Person p; + p.age = 21; + p.name = "John Doe"; + + libc::printf("%s is %d years old.", p.age, p.name); + + Person* pPtr = &p; + pPtr.age = 20; // Ok! + + libc::printf("%s is %d years old.", pPtr.age, pPtr.name); + +(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, optional [attributes](../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 printPerson(Person p) + { + libc::printf("%s is %d years old.", p.age, p.name); + } + + + ImportantPerson important_person; + important_person.age = 25; + important_person.name = "Jane Doe"; + important_person.title = "Rockstar"; + printPerson(important_person); // Only the first part of the struct is copied. + + +## Union types + +Union types are defined just like structs and are fully compatible with C. + + union Integral + { + byte as_byte; + short as_short; + int as_int; + long as_long; + } + +As usual unions are used to hold one of many possible values: + + 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. + libc::printf("%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; + } + + ... + + 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; + } + + ... + + Test t; + t.a = 0xABCD; + t.b = 0x789A; + char* c = (char*)&t; + io::printfn("%X", (uint)t); // Prints 789AABCD + for (int i = 0; i < 4; i++) io::printf("%X", c[i]); // Prints CDAB9A78 + 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; + } + + ... + + 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++) io::printf("%X", c[i]); // Prints CDAB9A78 on x86 + io::printn(); + c = (char*)&t2; + for (int i = 0; i < 4; i++) io::printf("%X", c[i]); // Prints ABCD789A + io::printn(); + +Bitstructs can be made to have ovelapping 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; + int b : 1..3; // Only valid due to the @overlap attribute + } + diff --git a/src/content/docs/references/docs/undefinedbehaviour.md b/src/content/docs/references/docs/undefinedbehaviour.md new file mode 100644 index 0000000..01f3bf1 --- /dev/null +++ b/src/content/docs/references/docs/undefinedbehaviour.md @@ -0,0 +1,80 @@ +--- +title: Undefined Behaviour +description: Undefined Behaviour +sidebar: + order: 123 +--- + +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 or vararray | 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. diff --git a/src/content/docs/references/docs/variables.md b/src/content/docs/references/docs/variables.md new file mode 100644 index 0000000..2842236 --- /dev/null +++ b/src/content/docs/references/docs/variables.md @@ -0,0 +1,23 @@ +--- +title: Variables +description: Variables +sidebar: + order: 114 +--- + + +### Zero init by default + +Unlike C, C3 local variables are zero-initialized by default. To avoid zero-init, 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 = {}; // boo 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. \ No newline at end of file diff --git a/src/content/docs/references/docs/vectors.md b/src/content/docs/references/docs/vectors.md new file mode 100644 index 0000000..b2bfe82 --- /dev/null +++ b/src/content/docs/references/docs/vectors.md @@ -0,0 +1,67 @@ +--- +title: Vectors +description: Vectors +sidebar: + order: 110 +--- + +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>]`. + +## Arithmetics on vectors + +Vectors support all arithmetics and other operations supported by its underlying type. The operations are +always performed elementwise. + + 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. +- `.distance(other)` - return the length of the difference of the two vectors. +- `.normalize()` - return a normalized vector. +- `.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: + + int[<3>] a = { 11, 22, 33 }; + int[<4>] b = a.xxzx; // b = { 11, 11, 33, 11 } + int c = b.w; // c = 11; + +## 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 them in vectors. +