diff --git a/index.html b/index.html index 198e5c1..2d7e9de 100644 --- a/index.html +++ b/index.html @@ -299,5 +299,5 @@

Thank yous

diff --git a/macros/index.html b/macros/index.html index 02560a7..a782e36 100644 --- a/macros/index.html +++ b/macros/index.html @@ -153,13 +153,13 @@
  • $vatype
  • $varef +
  • +
  • $vasplat
  • Untyped lists
  • -
  • Macro directives -
  • Reflection @@ -570,6 +570,14 @@

    $vatype

    $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:

    @@ -586,7 +594,6 @@

    Untyped lists

    // [1, 2] // 2 -

    Macro directives

    diff --git a/search/search_index.json b/search/search_index.json index 16a80e9..2b7b217 100644 --- a/search/search_index.json +++ b/search/search_index.json @@ -1 +1 @@ -{"config":{"lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"What is C3? C3 is a system programming language based on C. It is an evolution of C enabling the same paradigms and retaining the same syntax as far as possible. C3 started as an extension of the C2 language by Bas van den Berg . It has evolved significantly, not just in syntax but also in regard to error handling, macros, generics and strings. The C3 compiler can be found on github: https://github.com/c3lang/c3c . Binaries are directly downloadable for the following platforms: Windows x64 download , install instructions . Debian x64 download , install instructions . MacOS x64 download , install instructions . Official forums: Discord: invite link: https://discord.gg/qN76R87 Discourse: https://c3lang.discourse.group Last updated: Revision 2023-10-24 . Features Full C ABI compatibility Module system Generic modules Zero overhead errors Struct subtyping Semantic macro system Safe array access using sub arrays Zero cost simple gradual & opt-in pre/post conditions High level containers and string handling C to C3 conversion (for a subset of C) TODO LLVM backend Design principles Procedural \"get things done\"-type of language. Stay close to C - only change where there is a significant need. Flawless C integration. Learning C3 should be easy for a C programmer. Dare add conveniences if the value is great. Data is inert and zero is initialization. Avoid \"big ideas\". Avoid the kitchen sink language trap. Thank yous Special thank-yous to: Bas van der Berg (Author of C2 ), Jon Goodwin (Author of Cone ) and Andrey Penechko (Author of Vox ).","title":"About C3"},{"location":"#what-is-c3","text":"C3 is a system programming language based on C. It is an evolution of C enabling the same paradigms and retaining the same syntax as far as possible. C3 started as an extension of the C2 language by Bas van den Berg . It has evolved significantly, not just in syntax but also in regard to error handling, macros, generics and strings. The C3 compiler can be found on github: https://github.com/c3lang/c3c . Binaries are directly downloadable for the following platforms: Windows x64 download , install instructions . Debian x64 download , install instructions . MacOS x64 download , install instructions . Official forums: Discord: invite link: https://discord.gg/qN76R87 Discourse: https://c3lang.discourse.group Last updated: Revision 2023-10-24 .","title":"What is C3?"},{"location":"#features","text":"Full C ABI compatibility Module system Generic modules Zero overhead errors Struct subtyping Semantic macro system Safe array access using sub arrays Zero cost simple gradual & opt-in pre/post conditions High level containers and string handling C to C3 conversion (for a subset of C) TODO LLVM backend","title":"Features"},{"location":"#design-principles","text":"Procedural \"get things done\"-type of language. Stay close to C - only change where there is a significant need. Flawless C integration. Learning C3 should be easy for a C programmer. Dare add conveniences if the value is great. Data is inert and zero is initialization. Avoid \"big ideas\". Avoid the kitchen sink language trap.","title":"Design principles"},{"location":"#thank-yous","text":"Special thank-yous to: Bas van der Berg (Author of C2 ), Jon Goodwin (Author of Cone ) and Andrey Penechko (Author of Vox ).","title":"Thank yous"},{"location":"allfeatures/","text":"Feature summary Here is a summary of all the features of C3 and changes from C Symbols and literals Changes relating to literals, identifiers etc. Added 0o prefix for octal. 0b prefix for binary. Optional \"_\" as digit separator. Hexadecimal byte data, e.g x\"abcd\" . Base64 byte data, e.g. b64\"QzM=\" . Type name restrictions (PascalCase). Variable and function name restrictions (must start with lower case letter). Constant name restrictions (no lower case). Character literals may be 2, 4, 8, 16 bytes long. (2cc, 4cc etc). Raw string literals between \"`\". \\e escape character. Source code must be UTF-8. Assumes \\n for new row \\r is stripped from source. Bit-width integer and float suffixes: u8/i8/u16/i16/... f32/f64/... The null literal is a pointer value of 0. The true and false are boolean constants true and false. Removed Trigraphs / digraphs. 0123-style octal. z , LL and ULL suffixes. Built-in types Added Type declaration is left to right: int[4]*[2] a; instead of int (*a[2])[4]; Simd vector types using [<>] syntax, e.g. float[<4>] , use [<*>] for inferred length. Slice type built in, using [] suffix, e.g. int[] Distinct types, similar to a typedef but forms a new type. (Example: the String type is a distinct char[] ) Built-in 128-bit integer on all platforms. char is an unsigned 8-bit integer. ichar is its signed counterpart. Well-defined bitwidth for integer types: ichar/char (8 bits), short/ushort (16 bits), int/uint (32 bits), long/ulong (64 bits), int128/uint128 (128 bits) Pointer-sized iptr and uptr integers. isz and usz integers corresponding to the size_t bitwidth. Optional types are formed using the ! suffix. bool is the boolean type. typeid is a unique type identifier for a type, it can be used at runtime and compile time. any* contains a typeid and void* allowing it to act as a reference to any type of value. anyfault holds any fault value (see below). Changed Inferred array type uses [*] (e.g. int[*] x = { 1, 2 }; ). Flexible array member uses [*] . Removed The spiral rule type declaration (see above). Complex types size_t, ptrdiff_t (see above). Array types do not decay. Types Added bitstruct a struct with a container type allowing precise control over bit-layout, replacing bitfields and enum masks. fault an enum type with unique values which are used together with optional. Vector types. Optional types. enum allows a set of unique constants to be associated with each enum value. Compile time reflection and limited runtime reflection on types (see \"Reflection\") All types have a typeid property uniquely referring to that particular type. Distinct types, which are similar to aliases, but represent distinctly different types. Types may have methods. Methods can be added to any type, including built-in types. Subtyping: using inline on a struct member allows a struct to be implicitly converted to this member type and use corresponding methods. Using inline on a distinct type allows it to be implicitly converted to its base type (but not vice versa). Types may add operator overloading to support foreach and subscript operations. Generic types through generic modules, using (< ... >) for the generic parameter list (e.g. List() list; ). Interface types, any* types which allows dynamic invocation of methods. Changed typedef is replaced by def and has somewhat different syntax (e.g. def MyTypeAlias = int; ). Function pointer syntax is prefix fn followed by a regular function declaration without the function name. Removed Enums, structs and unions no longer have distinct namespaces. Enum, struct and union declarations should not have a trailing ';' Inline typedef is not allowed. def can only be used at the top level. Anonymous structs are not allowed. Type qualifiers are all removed, including const , restrict , volatile Function pointers types cannot be used \"raw\", but must always be used through a type alias. Introspection Compile time type methods: alignof , associated , elements , extnameof , inf , inner , kindof , len , max , membersof , min , nan , names , params , returns , sizeof , typeid , values , qnameof , is_eq , is_ordered . Runtime type methods: inner , kind , len , names , sizeof . Expressions Added Expression block using {| ... |} . Somewhat similar to GCC statement expressions. Array initializers may use ranges. (e.g. int[256] x = { [0..128] = 1 } ) ?: operator, returning the first value if it can be converted to a boolean true, otherwise the second value is returned. Orelse ?? returning the first value if it is a result, the second if the first value was an optional value. Rethrow ! suffix operator with an implicit return the value if it was an optional value. Dynamic calls, allowing calls to be made on the any* and interfaces dispatched using a dynamic mechanism. Create a slice using a range subscript (e.g. a[4..8] to form a slice from element 4 to element 8). Two range subscript methods: [start..inclusive_end] and [start:length] . Start, end and length may be omitted for default values. Indexing from end: slices, arrays and vectors may be indexed from the end using ^ . ^1 represents the last element. This works for ranges as well. Range assignment, assign a single value to an entire range e.g. a[4..8] = 1; . Slice assignment, copy one range to the other range e.g. a[4..8] = b[8..12]; . Array, vector and slice comparison: == can be used to make an element-wise comparison of two containers. ? suffix operator turns a fault into an optional value. !! suffix panics if the value is an optional value. $defined(...) returns true if the last expression is defined (sub-expressions must be valid). $and(...) $or(...) perform compile time && and || without semantically checking any elements after the first false/true respectively. Lambdas (anonymous functions) may be defined, they work just like functions and do not capture any state. Simple bitstructs (only containing booleans) may be manipulated using bit operations & ^ | ~ and assignment. Structs may implicitly convert to their inline member if they have one. Pointers to arrays may implicitly convert to slices. Any pointer may implicitly convert to an any* with type being the pointee. Optional values will implicitly invoke \"flatmap\" on an expression it is a subexpression of. Swizzling for arrays and vectors. Changed Compound literals use Type { ... } rather than (Type) { ... } Operator precedence of bit operations is higher than + and - . Well defined-evaluation order: left-to-right, assignment after expression evaluation. sizeof is $sizeof and only works on expressions. Use Type.sizeof on types. alignof is $alignof for expressions. Types use Type.alignof . Narrowing conversions are only allowed if all sub-expressions is as small or smaller than the type. Widening conversions are only allowed on simple expressions (i.e. most binary expressions and some unary may not be widened) Removed The comma operator is removed. Cast changes Functions Added Functions may be invoked using named arguments, the name is the dot-prefixed parameter name, e.g. foo(.name = a, .len = 2) . Typed varargs are declared Type... argument , and will take 0 or more arguments of the given type. It is possible to \"splat\" an array or slice into the location of a typed vararg using ... : foo(a, b, ...list) any* varargs are declared argument... , it can take 0 or more arguments of any type which are implicitly converted to the any* type. The function declaration may have @inline or @noinline as a default. Using @inline or @noinline on a function call expression will override the function default. Type methods are functions defined in the form fn void Foo.my_method(Foo* foo) { ... } , they can be invoked using dot syntax. Type methods may be attached to any type, even arrays and vectors. Error handling using optional return types. Changed Function declarations use the fn prefix. Removed Functions with C-style varargs may be called, and declared as external functions, but not used for C3 functions. Attributes C3 adds a long range of attributes in the form @name(...) . It is possible to create custom attribute groups using def (e.g. def MyAttribute(usz align) = { @aligned(align) @weak }; ) which groups certain attributes. Empty attribute groups are permitted. The complete list: @align , @benchmark , @bigendian , @builtin , @callconv , @deprecated , @dynamic , @export , @extern , @if , @inline , @interface , @littleendian , @local , @maydiscard , @naked , @nodiscard , @noinit , @noreturn , @nostrip , @obfuscate , @operator , @overlap , @priority , @private , @public , @pure , @reflect , @section , @test , @used , @unused . Declarations Added var declaration for type inferred variables in macros. E.g. var a = some_value; var declaration for new type variables in macros. E.g. var $Type = int; var declaration for compile time mutable variables in function and macros. E.g. var $foo = 1; const declarations may be untyped. Such constants are not stored in the resulting binary. Changed tlocal declares a variable to be thread local. static top level declarations are replaced with @local . ( static in functions is unchanged) Removed restrict removed. atomic should be replaced by atomic load/store operations. volatile should be replaced by volatile load/store operations. Statements Added Match-style variant of the switch statement, allows each case to hold an expression to test. Switching over type with typeid . Unpack any* to the underlying type with an any -switch. nextcase to fallthrough to the next case. nextcase to jump to the case with the expression value (this may be an expression evaluated at runtime). nextcase default to jump to the default clause. Labelled while / do / for / foreach to use with break nextcase and continue . foreach to iterate over arrays, vectors, slices and user-defined containers using operator overloading. foreach_r to iterate in reverse. foreach / foreach_r may take the element by value or reference. The index may optionally be provided. $if , $switch , $for , $foreach statements executing at compile time. $echo printing a message at compile time. $assert compile time assert. defer statement to execute statements at scope exit. defer catch and defer try similar to defer but executes only on optional exit or regular exit of scope respectively. do statements may omit while , behaving same as while (0) if may have a label. Labelled if may be exited using labelled break. asm blocks for inline assembly. if-try statements allows you to run code where an expression is a result. if-catch statements runs code on fault. It can be used to implicitly unwrap variables. Exhaustive switching on enums. Changed Switch cases will have implicit break, rather than implicit fallthrough. assert is an actual statement and may take a string or a format + arguments. static_assert is $assert and is a statement. Removed goto removed, replaced by labelled break, continue and nextcase. Compile time evaluation Added @if(cond) to conditionally include a struct/union field, a user-defined type etc. Compile time variables with $ prefix e.g. $foo . $if...$else...$endif and $switch...$endswitch inside of functions to conditionally include code. $for and $foreach to loop over compile time variables and data. $typeof determines an expression type without evaluating it. Type properties may be accessed at compile time. $checks determines if something may type check. $define returns true if the variable, function or type exists. $error emits an error if encountered. $embed includes a file as binary data. $include includes a file as text. $exec includes the output of a program as code. $evaltype takes a compile time string and turns it into a type. $eval takes a string and turns it into an identifier. $extnameof turns an identifier into its string external name. $nameof turns an identifier into its local string name. $qnameof turns an identifier into its local string name with the module prefixed. Compile time constant values are always compile time folded for arithmetic operations and casts. $$FUNCTION returns the current function as an identifier. Changed #define for constants is replaced by untyped constants, e.g. #define SOME_CONSTANT 1 becomes const SOME_CONSTANT = 1; . #define for variable and function aliases is replaced by def , e.g. #define native_foo win32_foo becomes def native_foo = win32_foo; In-function #if...#else..#endif is replaced by $if , #if...#elif...#endif is replaced by $switch . For converting code into a string use $stringify . Macros for date, line etc are replaced by $$DATE , $$FILE , $$FILEPATH , $$FUNC , $$LINE , $$MODULE , $$TIME . Removed Top level #if...#endif does not have a counterpart. Use @if instead. No #include directives, $include will include text but isn't for the same use. Macros Added macro for defining macros. \"Function-like\" macros have no prefix and has only regular parameters or type parameters. \"At\"-macros are prefixed with @ and may also have compile time values, expression and ref parameters, and may have a trailing body. Type parameters have the prefix $ and conform to the type naming standard (\"$TypeFoo\"). \"ref\" parameters are declared using with a & prefix operator. This is similar to C++ ref parameters. Expression parameters are unevaluated expressions, this is similar to arguments to #define . Compile time values have a $ prefix and must contain compile time constant values. Any macro that evaluates to a constant result can be used as if it was the resulting constant. Macros may be recursively evaluated. Macros are inlined at the location where they are invoked. Unless resulting in a single constant, macros implicitly create a runtime scope. Removed No #define macros. Macros cannot be incomplete statements. Features provided by builtins Some features are provided by builtins, and appears as normal functions and macros in the standard library but nonetheless provided unique functionality: @likely(...) / @unlikely(...) on branches affects compilation optimization. @anycast(...) casts an any* with an optional result. unreachable(...) marks a path as unreachable with a panic in safe mode. unsupported(...) similar to unreachable but for functionality not implemented. @expect(...) expect a certain value with an optional probability for the optimizer. @prefetch(...) prefect a pointer. swizzle(...) swizzles a vector. @volatile_load(...) and @volatile_store(...) volatile load/store. @atomic_load(...) and @atomic_store(...) atomic load/store. compare_exchange(...) atomic compare exchange. Saturating add, sub, mul, shl on integers. Vector reduce operations: add, mul, and, or, xor, max, min. Modules Modules are defined using module . Where name is on the form foo::bar::baz Modules can be split into an unlimited number of module sections, each starting with the same module name declaration. The import statement imports a given module. Each module section has its own set of import statements. Importing a module gives access to the declarations that are @public . Declarations are default @public , but a module section may set a different default (e.g. module my_module @private; ) @private means the declaration is only visible in the module. @local means only visible to the current module section. Imports are recursive. For example, import my_lib will implicitly also import my_lib::net . Multiple imports may be specified with the same import , e.g. import std::net, std::io; . Generic modules have a set of parameters after the module name module arr(); Generic modules are not type checked until any of its types, functions or globals are instantiated. Contracts Doc comments (starting with /** ) are parsed. The first part, up until the first @ directive is ignored. The @param directive for pointer arguments may define usage constraints [in] [out] and [inout] . Pointer argument constraints may add a & prefix to indicate that they may not be null , e.g. [&inout] . Contracts may be attached to generic modules, functions and macros. @require directives are evaluated given the arguments provided. Failing them may be a compile time or runtime error. The @ensure directive is evaluated at exit - if the return is a result and not an optional. return can be used as a variable identifier inside of @ensure , and holds the return value. @return! optionally lists the errors used. This will be checked at compile time. @pure says that no writing to globals is allowed inside and only @pure functions may be called. Benchmarking Benchmarks are indicated by @benchmark . Marking a module section @benchmark makes all functions inside of it implicitly benchmarks. Benchmarks are usually not compiled. Benchmarks are instead only run by the compiler on request. Testing Tests are indicated by @test . Marking a module section @test makes all functions inside of it implicitly tests. Tests are usually not compiled. Tests are instead only run by the compiler on request. Safe / fast Compilation has two modes: \"safe\" and \"fast\". Safe will insert checks for out-of-bounds access, null-pointer deref, shifting by negative numbers, division by zero, violation of contracts and asserts. Fast will assume all of those checks can be assumed to always pass. This means that unexpected behaviour may result from violating those checks. It is recommended to develop in \"safe\" mode. If debug symbols are available, C3 will produce a stack trace in safe mode where an error occurs.","title":"Feature list"},{"location":"allfeatures/#feature-summary","text":"Here is a summary of all the features of C3 and changes from C","title":"Feature summary"},{"location":"allfeatures/#symbols-and-literals","text":"Changes relating to literals, identifiers etc.","title":"Symbols and literals"},{"location":"allfeatures/#added","text":"0o prefix for octal. 0b prefix for binary. Optional \"_\" as digit separator. Hexadecimal byte data, e.g x\"abcd\" . Base64 byte data, e.g. b64\"QzM=\" . Type name restrictions (PascalCase). Variable and function name restrictions (must start with lower case letter). Constant name restrictions (no lower case). Character literals may be 2, 4, 8, 16 bytes long. (2cc, 4cc etc). Raw string literals between \"`\". \\e escape character. Source code must be UTF-8. Assumes \\n for new row \\r is stripped from source. Bit-width integer and float suffixes: u8/i8/u16/i16/... f32/f64/... The null literal is a pointer value of 0. The true and false are boolean constants true and false.","title":"Added"},{"location":"allfeatures/#removed","text":"Trigraphs / digraphs. 0123-style octal. z , LL and ULL suffixes.","title":"Removed"},{"location":"allfeatures/#built-in-types","text":"","title":"Built-in types"},{"location":"allfeatures/#added_1","text":"Type declaration is left to right: int[4]*[2] a; instead of int (*a[2])[4]; Simd vector types using [<>] syntax, e.g. float[<4>] , use [<*>] for inferred length. Slice type built in, using [] suffix, e.g. int[] Distinct types, similar to a typedef but forms a new type. (Example: the String type is a distinct char[] ) Built-in 128-bit integer on all platforms. char is an unsigned 8-bit integer. ichar is its signed counterpart. Well-defined bitwidth for integer types: ichar/char (8 bits), short/ushort (16 bits), int/uint (32 bits), long/ulong (64 bits), int128/uint128 (128 bits) Pointer-sized iptr and uptr integers. isz and usz integers corresponding to the size_t bitwidth. Optional types are formed using the ! suffix. bool is the boolean type. typeid is a unique type identifier for a type, it can be used at runtime and compile time. any* contains a typeid and void* allowing it to act as a reference to any type of value. anyfault holds any fault value (see below).","title":"Added"},{"location":"allfeatures/#changed","text":"Inferred array type uses [*] (e.g. int[*] x = { 1, 2 }; ). Flexible array member uses [*] .","title":"Changed"},{"location":"allfeatures/#removed_1","text":"The spiral rule type declaration (see above). Complex types size_t, ptrdiff_t (see above). Array types do not decay.","title":"Removed"},{"location":"allfeatures/#types","text":"","title":"Types"},{"location":"allfeatures/#added_2","text":"bitstruct a struct with a container type allowing precise control over bit-layout, replacing bitfields and enum masks. fault an enum type with unique values which are used together with optional. Vector types. Optional types. enum allows a set of unique constants to be associated with each enum value. Compile time reflection and limited runtime reflection on types (see \"Reflection\") All types have a typeid property uniquely referring to that particular type. Distinct types, which are similar to aliases, but represent distinctly different types. Types may have methods. Methods can be added to any type, including built-in types. Subtyping: using inline on a struct member allows a struct to be implicitly converted to this member type and use corresponding methods. Using inline on a distinct type allows it to be implicitly converted to its base type (but not vice versa). Types may add operator overloading to support foreach and subscript operations. Generic types through generic modules, using (< ... >) for the generic parameter list (e.g. List() list; ). Interface types, any* types which allows dynamic invocation of methods.","title":"Added"},{"location":"allfeatures/#changed_1","text":"typedef is replaced by def and has somewhat different syntax (e.g. def MyTypeAlias = int; ). Function pointer syntax is prefix fn followed by a regular function declaration without the function name.","title":"Changed"},{"location":"allfeatures/#removed_2","text":"Enums, structs and unions no longer have distinct namespaces. Enum, struct and union declarations should not have a trailing ';' Inline typedef is not allowed. def can only be used at the top level. Anonymous structs are not allowed. Type qualifiers are all removed, including const , restrict , volatile Function pointers types cannot be used \"raw\", but must always be used through a type alias.","title":"Removed"},{"location":"allfeatures/#introspection","text":"Compile time type methods: alignof , associated , elements , extnameof , inf , inner , kindof , len , max , membersof , min , nan , names , params , returns , sizeof , typeid , values , qnameof , is_eq , is_ordered . Runtime type methods: inner , kind , len , names , sizeof .","title":"Introspection"},{"location":"allfeatures/#expressions","text":"","title":"Expressions"},{"location":"allfeatures/#added_3","text":"Expression block using {| ... |} . Somewhat similar to GCC statement expressions. Array initializers may use ranges. (e.g. int[256] x = { [0..128] = 1 } ) ?: operator, returning the first value if it can be converted to a boolean true, otherwise the second value is returned. Orelse ?? returning the first value if it is a result, the second if the first value was an optional value. Rethrow ! suffix operator with an implicit return the value if it was an optional value. Dynamic calls, allowing calls to be made on the any* and interfaces dispatched using a dynamic mechanism. Create a slice using a range subscript (e.g. a[4..8] to form a slice from element 4 to element 8). Two range subscript methods: [start..inclusive_end] and [start:length] . Start, end and length may be omitted for default values. Indexing from end: slices, arrays and vectors may be indexed from the end using ^ . ^1 represents the last element. This works for ranges as well. Range assignment, assign a single value to an entire range e.g. a[4..8] = 1; . Slice assignment, copy one range to the other range e.g. a[4..8] = b[8..12]; . Array, vector and slice comparison: == can be used to make an element-wise comparison of two containers. ? suffix operator turns a fault into an optional value. !! suffix panics if the value is an optional value. $defined(...) returns true if the last expression is defined (sub-expressions must be valid). $and(...) $or(...) perform compile time && and || without semantically checking any elements after the first false/true respectively. Lambdas (anonymous functions) may be defined, they work just like functions and do not capture any state. Simple bitstructs (only containing booleans) may be manipulated using bit operations & ^ | ~ and assignment. Structs may implicitly convert to their inline member if they have one. Pointers to arrays may implicitly convert to slices. Any pointer may implicitly convert to an any* with type being the pointee. Optional values will implicitly invoke \"flatmap\" on an expression it is a subexpression of. Swizzling for arrays and vectors.","title":"Added"},{"location":"allfeatures/#changed_2","text":"Compound literals use Type { ... } rather than (Type) { ... } Operator precedence of bit operations is higher than + and - . Well defined-evaluation order: left-to-right, assignment after expression evaluation. sizeof is $sizeof and only works on expressions. Use Type.sizeof on types. alignof is $alignof for expressions. Types use Type.alignof . Narrowing conversions are only allowed if all sub-expressions is as small or smaller than the type. Widening conversions are only allowed on simple expressions (i.e. most binary expressions and some unary may not be widened)","title":"Changed"},{"location":"allfeatures/#removed_3","text":"The comma operator is removed.","title":"Removed"},{"location":"allfeatures/#cast-changes","text":"","title":"Cast changes"},{"location":"allfeatures/#functions","text":"","title":"Functions"},{"location":"allfeatures/#added_4","text":"Functions may be invoked using named arguments, the name is the dot-prefixed parameter name, e.g. foo(.name = a, .len = 2) . Typed varargs are declared Type... argument , and will take 0 or more arguments of the given type. It is possible to \"splat\" an array or slice into the location of a typed vararg using ... : foo(a, b, ...list) any* varargs are declared argument... , it can take 0 or more arguments of any type which are implicitly converted to the any* type. The function declaration may have @inline or @noinline as a default. Using @inline or @noinline on a function call expression will override the function default. Type methods are functions defined in the form fn void Foo.my_method(Foo* foo) { ... } , they can be invoked using dot syntax. Type methods may be attached to any type, even arrays and vectors. Error handling using optional return types.","title":"Added"},{"location":"allfeatures/#changed_3","text":"Function declarations use the fn prefix.","title":"Changed"},{"location":"allfeatures/#removed_4","text":"Functions with C-style varargs may be called, and declared as external functions, but not used for C3 functions.","title":"Removed"},{"location":"allfeatures/#attributes","text":"C3 adds a long range of attributes in the form @name(...) . It is possible to create custom attribute groups using def (e.g. def MyAttribute(usz align) = { @aligned(align) @weak }; ) which groups certain attributes. Empty attribute groups are permitted. The complete list: @align , @benchmark , @bigendian , @builtin , @callconv , @deprecated , @dynamic , @export , @extern , @if , @inline , @interface , @littleendian , @local , @maydiscard , @naked , @nodiscard , @noinit , @noreturn , @nostrip , @obfuscate , @operator , @overlap , @priority , @private , @public , @pure , @reflect , @section , @test , @used , @unused .","title":"Attributes"},{"location":"allfeatures/#declarations","text":"","title":"Declarations"},{"location":"allfeatures/#added_5","text":"var declaration for type inferred variables in macros. E.g. var a = some_value; var declaration for new type variables in macros. E.g. var $Type = int; var declaration for compile time mutable variables in function and macros. E.g. var $foo = 1; const declarations may be untyped. Such constants are not stored in the resulting binary.","title":"Added"},{"location":"allfeatures/#changed_4","text":"tlocal declares a variable to be thread local. static top level declarations are replaced with @local . ( static in functions is unchanged)","title":"Changed"},{"location":"allfeatures/#removed_5","text":"restrict removed. atomic should be replaced by atomic load/store operations. volatile should be replaced by volatile load/store operations.","title":"Removed"},{"location":"allfeatures/#statements","text":"","title":"Statements"},{"location":"allfeatures/#added_6","text":"Match-style variant of the switch statement, allows each case to hold an expression to test. Switching over type with typeid . Unpack any* to the underlying type with an any -switch. nextcase to fallthrough to the next case. nextcase to jump to the case with the expression value (this may be an expression evaluated at runtime). nextcase default to jump to the default clause. Labelled while / do / for / foreach to use with break nextcase and continue . foreach to iterate over arrays, vectors, slices and user-defined containers using operator overloading. foreach_r to iterate in reverse. foreach / foreach_r may take the element by value or reference. The index may optionally be provided. $if , $switch , $for , $foreach statements executing at compile time. $echo printing a message at compile time. $assert compile time assert. defer statement to execute statements at scope exit. defer catch and defer try similar to defer but executes only on optional exit or regular exit of scope respectively. do statements may omit while , behaving same as while (0) if may have a label. Labelled if may be exited using labelled break. asm blocks for inline assembly. if-try statements allows you to run code where an expression is a result. if-catch statements runs code on fault. It can be used to implicitly unwrap variables. Exhaustive switching on enums.","title":"Added"},{"location":"allfeatures/#changed_5","text":"Switch cases will have implicit break, rather than implicit fallthrough. assert is an actual statement and may take a string or a format + arguments. static_assert is $assert and is a statement.","title":"Changed"},{"location":"allfeatures/#removed_6","text":"goto removed, replaced by labelled break, continue and nextcase.","title":"Removed"},{"location":"allfeatures/#compile-time-evaluation","text":"","title":"Compile time evaluation"},{"location":"allfeatures/#added_7","text":"@if(cond) to conditionally include a struct/union field, a user-defined type etc. Compile time variables with $ prefix e.g. $foo . $if...$else...$endif and $switch...$endswitch inside of functions to conditionally include code. $for and $foreach to loop over compile time variables and data. $typeof determines an expression type without evaluating it. Type properties may be accessed at compile time. $checks determines if something may type check. $define returns true if the variable, function or type exists. $error emits an error if encountered. $embed includes a file as binary data. $include includes a file as text. $exec includes the output of a program as code. $evaltype takes a compile time string and turns it into a type. $eval takes a string and turns it into an identifier. $extnameof turns an identifier into its string external name. $nameof turns an identifier into its local string name. $qnameof turns an identifier into its local string name with the module prefixed. Compile time constant values are always compile time folded for arithmetic operations and casts. $$FUNCTION returns the current function as an identifier.","title":"Added"},{"location":"allfeatures/#changed_6","text":"#define for constants is replaced by untyped constants, e.g. #define SOME_CONSTANT 1 becomes const SOME_CONSTANT = 1; . #define for variable and function aliases is replaced by def , e.g. #define native_foo win32_foo becomes def native_foo = win32_foo; In-function #if...#else..#endif is replaced by $if , #if...#elif...#endif is replaced by $switch . For converting code into a string use $stringify . Macros for date, line etc are replaced by $$DATE , $$FILE , $$FILEPATH , $$FUNC , $$LINE , $$MODULE , $$TIME .","title":"Changed"},{"location":"allfeatures/#removed_7","text":"Top level #if...#endif does not have a counterpart. Use @if instead. No #include directives, $include will include text but isn't for the same use.","title":"Removed"},{"location":"allfeatures/#macros","text":"","title":"Macros"},{"location":"allfeatures/#added_8","text":"macro for defining macros. \"Function-like\" macros have no prefix and has only regular parameters or type parameters. \"At\"-macros are prefixed with @ and may also have compile time values, expression and ref parameters, and may have a trailing body. Type parameters have the prefix $ and conform to the type naming standard (\"$TypeFoo\"). \"ref\" parameters are declared using with a & prefix operator. This is similar to C++ ref parameters. Expression parameters are unevaluated expressions, this is similar to arguments to #define . Compile time values have a $ prefix and must contain compile time constant values. Any macro that evaluates to a constant result can be used as if it was the resulting constant. Macros may be recursively evaluated. Macros are inlined at the location where they are invoked. Unless resulting in a single constant, macros implicitly create a runtime scope.","title":"Added"},{"location":"allfeatures/#removed_8","text":"No #define macros. Macros cannot be incomplete statements.","title":"Removed"},{"location":"allfeatures/#features-provided-by-builtins","text":"Some features are provided by builtins, and appears as normal functions and macros in the standard library but nonetheless provided unique functionality: @likely(...) / @unlikely(...) on branches affects compilation optimization. @anycast(...) casts an any* with an optional result. unreachable(...) marks a path as unreachable with a panic in safe mode. unsupported(...) similar to unreachable but for functionality not implemented. @expect(...) expect a certain value with an optional probability for the optimizer. @prefetch(...) prefect a pointer. swizzle(...) swizzles a vector. @volatile_load(...) and @volatile_store(...) volatile load/store. @atomic_load(...) and @atomic_store(...) atomic load/store. compare_exchange(...) atomic compare exchange. Saturating add, sub, mul, shl on integers. Vector reduce operations: add, mul, and, or, xor, max, min.","title":"Features provided by builtins"},{"location":"allfeatures/#modules","text":"Modules are defined using module . Where name is on the form foo::bar::baz Modules can be split into an unlimited number of module sections, each starting with the same module name declaration. The import statement imports a given module. Each module section has its own set of import statements. Importing a module gives access to the declarations that are @public . Declarations are default @public , but a module section may set a different default (e.g. module my_module @private; ) @private means the declaration is only visible in the module. @local means only visible to the current module section. Imports are recursive. For example, import my_lib will implicitly also import my_lib::net . Multiple imports may be specified with the same import , e.g. import std::net, std::io; . Generic modules have a set of parameters after the module name module arr(); Generic modules are not type checked until any of its types, functions or globals are instantiated.","title":"Modules"},{"location":"allfeatures/#contracts","text":"Doc comments (starting with /** ) are parsed. The first part, up until the first @ directive is ignored. The @param directive for pointer arguments may define usage constraints [in] [out] and [inout] . Pointer argument constraints may add a & prefix to indicate that they may not be null , e.g. [&inout] . Contracts may be attached to generic modules, functions and macros. @require directives are evaluated given the arguments provided. Failing them may be a compile time or runtime error. The @ensure directive is evaluated at exit - if the return is a result and not an optional. return can be used as a variable identifier inside of @ensure , and holds the return value. @return! optionally lists the errors used. This will be checked at compile time. @pure says that no writing to globals is allowed inside and only @pure functions may be called.","title":"Contracts"},{"location":"allfeatures/#benchmarking","text":"Benchmarks are indicated by @benchmark . Marking a module section @benchmark makes all functions inside of it implicitly benchmarks. Benchmarks are usually not compiled. Benchmarks are instead only run by the compiler on request.","title":"Benchmarking"},{"location":"allfeatures/#testing","text":"Tests are indicated by @test . Marking a module section @test makes all functions inside of it implicitly tests. Tests are usually not compiled. Tests are instead only run by the compiler on request.","title":"Testing"},{"location":"allfeatures/#safe-fast","text":"Compilation has two modes: \"safe\" and \"fast\". Safe will insert checks for out-of-bounds access, null-pointer deref, shifting by negative numbers, division by zero, violation of contracts and asserts. Fast will assume all of those checks can be assumed to always pass. This means that unexpected behaviour may result from violating those checks. It is recommended to develop in \"safe\" mode. If debug symbols are available, C3 will produce a stack trace in safe mode where an error occurs.","title":"Safe / fast"},{"location":"anyinterfaces/","text":"Any and interfaces 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.","title":"Any and interfaces"},{"location":"anyinterfaces/#any-and-interfaces","text":"","title":"Any and interfaces"},{"location":"anyinterfaces/#working-with-the-type-of-any-at-runtime","text":"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);","title":"Working with the type of any* at runtime."},{"location":"anyinterfaces/#variable-argument-functions-with-implicit-any","text":"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:","title":"Variable argument functions with implicit any"},{"location":"anyinterfaces/#explicit-any-vararg-functions","text":"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:","title":"Explicit any vararg functions"},{"location":"anyinterfaces/#implicit-any-vararg-functions","text":"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.","title":"Implicit any vararg functions"},{"location":"anyinterfaces/#interfaces","text":"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 .","title":"Interfaces"},{"location":"anyinterfaces/#defining-an-interface","text":"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.","title":"Defining an interface"},{"location":"anyinterfaces/#implementing-the-interface","text":"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); }","title":"Implementing the interface"},{"location":"anyinterfaces/#dynamic-methods","text":"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.","title":"\"@dynamic\" methods"},{"location":"anyinterfaces/#referring-to-an-interface-by-pointer","text":"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\"","title":"Referring to an interface by pointer"},{"location":"anyinterfaces/#calling-dynamic-methods","text":"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!\" }","title":"Calling dynamic methods"},{"location":"anyinterfaces/#reflection-invocation","text":"This functionality is not yet implemented and may see syntax changes It is possible to retrieve any @dynamic function by name and invoke it: def VoidMethodFn = fn void(void*); fn void* int.test_something(&self) @dynamic { io::printfn(\"Testing: %d\", *self); } fn void main() { int z = 321; any* a = &z; VoidMethodFn test_func = a.reflect(\"test_something\"); test_func(a); // Will print \"Testing: 321\" } This feature allows methods to be linked up at runtime.","title":"Reflection invocation"},{"location":"arrays/","text":"Arrays 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","title":"Arrays"},{"location":"arrays/#arrays","text":"Arrays has a central role in programming. C3 offers 2 built-in types of arrays:","title":"Arrays"},{"location":"arrays/#fixed-arrays","text":"[] 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]","title":"Fixed arrays"},{"location":"arrays/#subarray","text":"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.","title":"Subarray"},{"location":"arrays/#slicing-arrays","text":"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.","title":"Slicing arrays"},{"location":"arrays/#conversion-list","text":"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];","title":"Conversion list"},{"location":"arrays/#internals","text":"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; }","title":"Internals"},{"location":"arrays/#iteration-over-arrays","text":"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","title":"Iteration over arrays"},{"location":"asm/","text":"Inline assembly C3 provides two ways to insert inline assembly: asm strings and asm blocks. Asm strings This form takes a single compile time string and passes it directly to the underlying backend without any changes. int x = 0; asm(\"nop\"); int y = x; Asm block Asm blocks uses a common grammar for all types of processors. It assumes that all assembly statements can be reduced to the format: instruction (arg (',' arg)*)?; Where an arg is: An identifier, e.g. FOO , x . A numeric constant 1 0xFF etc. A register name (always lower case with a '$' prefix) e.g. $eax $r7 . The address of a variable e.g. &x . An indirect address: [addr] or [addr + index * + offset] . Any expression inside of \"()\" (will be evaluated before entering the asm block). An example: int aa = 3; int g; int* gp = &g; int* xa = &a; usz asf = 1; asm { movl x, 4; // Move 4 into the variable x movl [gp], x; // Move the value of x into the address in gp movl x, 1; // Move 1 into x movl [xa + asf * 4 + 4], x; // Move x into the address at xa[asf + 1] movl $eax, (23 + x); // Move 23 + x into EAX movl x, $eax; // Move EAX into x movq [&z], 33; // Move 33 into the memory address of z } The asm block will infer register clobbers and in/out parameters. *Please note that the current state of inline asm is a work in progress , only a subset of x86 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.","title":"Inline asm"},{"location":"asm/#inline-assembly","text":"C3 provides two ways to insert inline assembly: asm strings and asm blocks.","title":"Inline assembly"},{"location":"asm/#asm-strings","text":"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;","title":"Asm strings"},{"location":"asm/#asm-block","text":"Asm blocks uses a common grammar for all types of processors. It assumes that all assembly statements can be reduced to the format: instruction (arg (',' arg)*)?; Where an arg is: An identifier, e.g. FOO , x . A numeric constant 1 0xFF etc. A register name (always lower case with a '$' prefix) e.g. $eax $r7 . The address of a variable e.g. &x . An indirect address: [addr] or [addr + index * + offset] . Any expression inside of \"()\" (will be evaluated before entering the asm block). An example: int aa = 3; int g; int* gp = &g; int* xa = &a; usz asf = 1; asm { movl x, 4; // Move 4 into the variable x movl [gp], x; // Move the value of x into the address in gp movl x, 1; // Move 1 into x movl [xa + asf * 4 + 4], x; // Move x into the address at xa[asf + 1] movl $eax, (23 + x); // Move 23 + x into EAX movl x, $eax; // Move EAX into x movq [&z], 33; // Move 33 into the memory address of z } The asm block will infer register clobbers and in/out parameters. *Please note that the current state of inline asm is a work in progress , only a subset of x86 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.","title":"Asm block"},{"location":"attributes/","text":"Attributes 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 = {};","title":"Attributes"},{"location":"attributes/#attributes","text":"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.","title":"Attributes"},{"location":"attributes/#built-in-attributes","text":"","title":"Built in attributes"},{"location":"attributes/#alignalignment-struct-bitstructs-union-var-fn","text":"This attribute sets the minimum alignment for a field or a variable.","title":"@align(alignment) (struct, bitstructs, union, var, fn)"},{"location":"attributes/#benchmark-fn","text":"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.","title":"@benchmark (fn)"},{"location":"attributes/#bigendian-bitstruct","text":"Lays out the bits as if the data was stored in a big endian type, regardless of host system endianness.","title":"@bigendian (bitstruct)"},{"location":"attributes/#builtin-fn-macro-global-const","text":"Allows a macro, function, global or constant be used from another module without the module path prefixed. Should be used sparingly.","title":"@builtin (fn, macro, global, const)"},{"location":"attributes/#callc-fn","text":"Sets the call convention, which may be ignored if the convention is not supported on the target. Valid arguments are veccall , ccall , stdcall .","title":"@callc (fn)"},{"location":"attributes/#deprecated-types-fn-macro-global-const-member","text":"Marks the particular type, global, const or member as deprecated, making use trigger a warning.","title":"@deprecated (types, fn, macro, global, const, member)"},{"location":"attributes/#export-fn-global-const-enum-union-struct-fault","text":"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.","title":"@export (fn, global, const, enum, union, struct, fault)"},{"location":"attributes/#extern-fn-global-const-enum-union-struct-fault","text":"Sets the external (linkage) name of this declaration.","title":"@extern (fn, global, const, enum, union, struct, fault)"},{"location":"attributes/#finalizer-function","text":"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.","title":"@finalizer (function)"},{"location":"attributes/#if-all-declarations","text":"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.","title":"@if (all declarations)"},{"location":"attributes/#init-function","text":"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.","title":"@init (function)"},{"location":"attributes/#inline-fn-call","text":"Declares a function to always be inlined or if placed on a call, that the call should be inlined.","title":"@inline (fn, call)"},{"location":"attributes/#littleendian-bitstruct","text":"Lays out the bits as if the data was stored in a little endian type, regardless of host system endianness.","title":"@littleendian (bitstruct)"},{"location":"attributes/#local-any-declaration","text":"Sets the visibility to \"local\", which means it's only visible in the current module section.","title":"@local (any declaration)"},{"location":"attributes/#maydiscard-fn-macro","text":"Allows the return value of the function or macro to be discarded even if it is an optional. Should be used sparingly.","title":"@maydiscard (fn, macro)"},{"location":"attributes/#naked-fn","text":"This attribute disables prologue / epilogue emission for the function.","title":"@naked (fn)"},{"location":"attributes/#nodiscard-fn-macro","text":"The return value may not be discarded.","title":"@nodiscard (fn, macro)"},{"location":"attributes/#noinit-global-local","text":"Prevents the compiler from zero initializing the variable.","title":"@noinit (global, local)"},{"location":"attributes/#noreturn-fn","text":"Declares that the function will never return.","title":"@noreturn (fn)"},{"location":"attributes/#nostrip-any-declaration","text":"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.","title":"@nostrip (any declaration)"},{"location":"attributes/#obfuscate-any-declaration","text":"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.","title":"@obfuscate (any declaration)"},{"location":"attributes/#operator-method-macro-method","text":"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.","title":"@operator (method, macro method)"},{"location":"attributes/#overlap-bitstruct","text":"Allows bitstruct fields to have overlapping bit ranges.","title":"@overlap (bitstruct)"},{"location":"attributes/#packed-struct-union","text":"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 .","title":"@packed (struct, union)"},{"location":"attributes/#private-any-declaration","text":"Sets the visibility to \"private\", which means it is visible in the same module, but not from other modules.","title":"@private (any declaration)"},{"location":"attributes/#pure-call","text":"Used to annotate a non pure function as \"pure\" when checking for conformance to @pure on functions.","title":"@pure (call)"},{"location":"attributes/#packed-struct-union-enum","text":"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.","title":"@packed (struct, union, enum)"},{"location":"attributes/#reflect-any-declaration","text":"Adds additional reflection information. Has no effect currently.","title":"@reflect (any declaration)"},{"location":"attributes/#sectionname-fn-const-global","text":"Declares that a global variable or function should appear in a specific section.","title":"@section(name) (fn, const, global)"},{"location":"attributes/#test-fn","text":"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.","title":"@test (fn)"},{"location":"attributes/#unused-any-declaration","text":"Marks the declaration as possibly unused (but should not emit a warning).","title":"@unused (any declaration)"},{"location":"attributes/#used-any-declaration","text":"Marks a parameter, value etc. as must being used.","title":"@used (any declaration)"},{"location":"attributes/#weak-fn-const-global","text":"Emits a weak symbol rather than a global.","title":"@weak (fn, const, global)"},{"location":"attributes/#user-defined-attributes","text":"User defined attributes are intended for conditional application of built-in attributes. def @MyAttribute = { @noreturn @inline }; // The following two are equivalent: fn void foo() @MyAttribute { ... } fn void foo() @noreturn @inline { ... } A user defined attribute may also be completely empty: def @MyAttributeEmpty = {};","title":"User defined attributes"},{"location":"buildcommands/","text":"Build Commands When starting out, with C3 it's natural to use run to try things out. For larger projects, the built-in build system is instead recommended. By default the compiler is compiling stand-alone files to output an executable binary. c3c run The run command works same as compilation, but also immediately runs the resulting executable. c3c run Common additional parameters Additional parameters: - --lib add a library to search. - --output override the output directory. - --path execute as if standing at init c3c init [optional path] . Create a new project structure in the current directory. Use the --template to select a template. The following are built in: default - the default template, produces an executable. lib - template for producing a library. staticlib - template for producing a static library. It is also possible to give the path to a custom template. Additional parameters: - --template indicate an alternative template to use. c3c init hello_world will create the following structure: $ tree . . \u2514\u2500\u2500 hello_world \u251c\u2500\u2500 LICENSE \u251c\u2500\u2500 README.md \u251c\u2500\u2500 build \u251c\u2500\u2500 docs \u2502 \u251c\u2500\u2500 about.md \u2502 \u2514\u2500\u2500 src \u2502 \u2514\u2500\u2500 index.html \u251c\u2500\u2500 lib \u251c\u2500\u2500 project.c3p \u251c\u2500\u2500 resources \u251c\u2500\u2500 src \u2502 \u2514\u2500\u2500 hello_world \u2502 \u2514\u2500\u2500 main.c3 \u2514\u2500\u2500 test \u2514\u2500\u2500 hello_world build build [target] Build the project in the current path. It doesn't matter where in the project structure you are. The built-in templates define two targets: debug (which is the default) and release . clean clean build-run build-run [target] Build the target (if needed) and run the executable. clean-run clean-run [target] Clean, build and run the target. dist dist [target] Clean, build and package the target. docs docs [target] Rebuilds the documentation. bench bench [target] Runs benchmarks on a target.","title":"Commands"},{"location":"buildcommands/#build-commands","text":"When starting out, with C3 it's natural to use run to try things out. For larger projects, the built-in build system is instead recommended. By default the compiler is compiling stand-alone files to output an executable binary. c3c ","title":"Build Commands"},{"location":"buildcommands/#run","text":"The run command works same as compilation, but also immediately runs the resulting executable. c3c run ","title":"run"},{"location":"buildcommands/#common-additional-parameters","text":"Additional parameters: - --lib add a library to search. - --output override the output directory. - --path execute as if standing at","title":"Common additional parameters"},{"location":"buildcommands/#init","text":"c3c init [optional path] . Create a new project structure in the current directory. Use the --template to select a template. The following are built in: default - the default template, produces an executable. lib - template for producing a library. staticlib - template for producing a static library. It is also possible to give the path to a custom template. Additional parameters: - --template indicate an alternative template to use. c3c init hello_world will create the following structure: $ tree . . \u2514\u2500\u2500 hello_world \u251c\u2500\u2500 LICENSE \u251c\u2500\u2500 README.md \u251c\u2500\u2500 build \u251c\u2500\u2500 docs \u2502 \u251c\u2500\u2500 about.md \u2502 \u2514\u2500\u2500 src \u2502 \u2514\u2500\u2500 index.html \u251c\u2500\u2500 lib \u251c\u2500\u2500 project.c3p \u251c\u2500\u2500 resources \u251c\u2500\u2500 src \u2502 \u2514\u2500\u2500 hello_world \u2502 \u2514\u2500\u2500 main.c3 \u2514\u2500\u2500 test \u2514\u2500\u2500 hello_world","title":"init"},{"location":"buildcommands/#build","text":"build [target] Build the project in the current path. It doesn't matter where in the project structure you are. The built-in templates define two targets: debug (which is the default) and release .","title":"build"},{"location":"buildcommands/#clean","text":"clean","title":"clean"},{"location":"buildcommands/#build-run","text":"build-run [target] Build the target (if needed) and run the executable.","title":"build-run"},{"location":"buildcommands/#clean-run","text":"clean-run [target] Clean, build and run the target.","title":"clean-run"},{"location":"buildcommands/#dist","text":"dist [target] Clean, build and package the target.","title":"dist"},{"location":"buildcommands/#docs","text":"docs [target] Rebuilds the documentation.","title":"docs"},{"location":"buildcommands/#bench","text":"bench [target] Runs benchmarks on a target.","title":"bench"},{"location":"buildintro/","text":"Build System Building a project is done by invoking the C3 compiler with the build or run command inside of the project structure. The compiler will search upwards in the file hierarchy until a project.json file is found.","title":"Introduction"},{"location":"buildintro/#build-system","text":"Building a project is done by invoking the C3 compiler with the build or run command inside of the project structure. The compiler will search upwards in the file hierarchy until a project.json file is found.","title":"Build System"},{"location":"builtins/","text":"Builtins 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.","title":"Builtins"},{"location":"builtins/#builtins","text":"The compiler offers builtin constants and functions. Some are only available on certain targets. All builtins use the $$ name prefix.","title":"Builtins"},{"location":"builtins/#builtin-constants","text":"These can all safely be used by the user.","title":"Builtin constants"},{"location":"builtins/#benchmark_names","text":"An array of names of the benchmark functions.","title":"$$BENCHMARK_NAMES"},{"location":"builtins/#benchmark_fns","text":"An array of addresses to the benchmark functions.","title":"$$BENCHMARK_FNS"},{"location":"builtins/#date","text":"The current date.","title":"$$DATE"},{"location":"builtins/#file","text":"The current file name.","title":"$$FILE"},{"location":"builtins/#filepath","text":"The current file with path.","title":"$$FILEPATH"},{"location":"builtins/#func","text":"The current function name, will return \" \" on the global level.","title":"$$FUNC"},{"location":"builtins/#function","text":"The current function as an expression.","title":"$$FUNCTION"},{"location":"builtins/#line","text":"The current line as an integer.","title":"$$LINE"},{"location":"builtins/#line_raw","text":"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.","title":"$$LINE_RAW"},{"location":"builtins/#module","text":"The current module name.","title":"$$MODULE"},{"location":"builtins/#time","text":"The current time.","title":"$$TIME"},{"location":"builtins/#builtin-functions","text":"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.","title":"Builtin functions"},{"location":"builtins/#trap","text":"Emits a trap instruction.","title":"$$trap"},{"location":"builtins/#unreachable","text":"Inserts an \"unreachable\" annotation.","title":"$$unreachable"},{"location":"builtins/#stacktrace","text":"Returns the current \"callstack\" reference if available. Compiler dependent.","title":"$$stacktrace"},{"location":"builtins/#volatile_store","text":"Takes a variable and a value and stores the value as a volatile store.","title":"$$volatile_store"},{"location":"builtins/#volatile_load","text":"Takes a variable and returns the value using a volatile load.","title":"$$volatile_load"},{"location":"builtins/#memcpy","text":"Builtin memcpy instruction.","title":"$$memcpy"},{"location":"builtins/#memset","text":"Builtin memset instruction.","title":"$$memset"},{"location":"builtins/#prefetch","text":"Prefetch a memory location.","title":"$$prefetch"},{"location":"builtins/#sysclock","text":"Access to the cycle counter register (or similar low latency clock) on supported architectures (e.g. RDTSC on x86), otherwise $$sysclock will yield 0.","title":"$$sysclock"},{"location":"builtins/#syscall","text":"Makes a syscall according to the platform convention on platforms where it is supported.","title":"$$syscall"},{"location":"builtins/#math-functions","text":"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 .","title":"Math functions"},{"location":"builtins/#bit-functions","text":"","title":"Bit functions"},{"location":"builtins/#fshl-and-fshr","text":"Funnel shift left and right, takes either two integers or two integer vectors.","title":"$$fshl and $$fshr"},{"location":"builtins/#ctz-clz-bitreverse-bswap-popcount","text":"Bit functions work on an integer or an integer vector.","title":"$$ctz, $$clz, $$bitreverse, $$bswap, $$popcount"},{"location":"builtins/#vector-functions","text":"$$reduce_add , $$reduce_mul , $$reduce_and , $$reduce_or , $$reduce_xor work on integer vectors. $$reduce_fadd , $$reduce_fmul works on float vectors. $$reduce_max , $$reduce_min works on any vector. $$reverse reverses the values in any vector. $$shufflevector rearranges the values of two vectors using a fixed mask into a resulting vector.","title":"Vector functions"},{"location":"changes/","text":"Change log Revision 2023-10-24 Removal of $checks and @checked Additional chapter on vectors. Addition of $and / $or . Added is_eq and is_ordered type properties. Revision 2023-10-20 protocol => interface Overall update to reflection docs. Updates to distinct. Revision 2023-10-05 protocol Update to any* Revision 2023-10-03 exec and $exec change of static initializer Revision 2023-09-09 Removal of implicit void! -> anyfault conversion. Change @catchof to @catch . Revision 2023-07-22 Feature list. Macro ref parameter update. Abbreviated method first arg. Revision 2023-07-09 Update with $embed information. Revision 2023-07-07 Updated generics syntax and uses. Moved all \"ideas\" to the issue tracker. Revision 2023-06-23 Distinct inline explanation. Module sections. Revision 2023-06-11 Updated list of attributes. Better @if examples. Revision 2023-06-10 Updated with @if directive. Revision 2023-06-03 Added specific compiletime page. Updated examples with references. variant -> any, Revision 2023-06-05 Chapter on dynamic code. Updated some pages with references to dynamic calls. Revision 2023-06-02 Suffix '?' replaced by '!' and vice versa. Updates to ct statements. Fixes to array descriptions Replace try? / catch? with macros Updates to reflection define/typedef is now \"def\" Revision 2023-03-18 try? / catch? println -> printn Revision 2023-02-15 Added uint128 / int128 to the documentation. @private, @public and @local Revision 2023-02-14 Updated typedef / define Revision 2023-02-11 Changed @extname to @extern following compiler changes. Document @export Revision 2023-01-24 Consistently use printfn rather than printfln Added short function syntax. Added lambdas . Revision 2023-01-07 Direct download links added. Prefer \"String\" over char[] Revision 2022-12-28 Bitstruct documented. Revision 2022-11-18 Documented inline asm. Revision 2022-11-16 Move description of static initialize. Info on operator overload. More details on function pointers. Updated project tree. Revision 2022-11-07 Added information on enum associated values. Revision 2022-10-31 Updated project.json description. Added information about static initializers and finalizers. Revision 2022-10-14 libc::printf replaced with io::printf Revision 2022-10-01 Expanded and updated types . Revision 2022-07-20 Added start + len syntax Revision 2022-07-15 Added \"builtins\" Revision 2022-06-30 Updates to module system Revision 2022-04-05 Removal of @opaque . Revision 2022-03-26 Remove escape macros. Adding stringify and first class names. Removing \"?? jump\" Revision 2022-03-15 Removal of multi-line strings Revision 2022-03-04 Updates to $sizeof. Addition of $eval / $evaltype. Removal of $unreachable. Revision 2022-02-16 Updates to imports. Updates to project files. Revision 2022-02-09 Major revision to bring everything up to date. Revision 2021-10-20 func replaced by fn Compound literal now Type { ... } like C++. Update of conversion rules New error syntax Revision 2021-08-27 Updated reflection functionality. Added documentation for multi-line strings. Added documentation for base64 and hex array literals. Revision 2021-08-12 Updated error type and error handling with try/catch Revision 2021-07-13 Added nesting to / ... / removed /+ ... +/ Added primer. Revision 2021-06-20 Updated array layout. Revised macros for foreach. Removed old generic functions. Added new ideas around generic, macros Changed macro body definition syntax. Introduced both $for and $foreach. Revision 2021-05-31 Removal of vararray type. Updated user defined attributes. Removed incremental arrays. Added information on define . Added private modules and import. Revision 2021-05-18 Change cast to (type)(expression) Revision 2021-05-08 Added rationale for some changes from C. Updated undefined and undefined behaviour . Removed many of the fine-grained module features. Removed \"local\" visibility in modules . All modules are now distinct, parent modules do not have any special access to submodules. Added as module imports. Revision 2021-04-05 \"next\" is now \"nextcase\". Added link to the C3 discord. The conversions page updated with new conversion rules. Updated compound literal syntax. Removed undefined behaviour behaviour on integer overflow and added a list of unspecified behaviour. Revision 2020-12-23 Updated slice behaviour. Updated expression block syntax. Added link to specification-in-progress. Revision 2020-12-04 Local variables are implicitly zero. Removed in-block declarations. Changed struct member initialization syntax. Changed named parameter syntax. Updated on macro syntax. Removed built in c types. Revision 2020-08-22 Added slice operations. Changed cast syntax to cast( as ) . Revision 2020-07-08 Additions to error handling . Introduction of labelled nextcase , break and continue . Removal of goto . Revision 2020-06-17 Alternate casts in idea . Method functions simply renamed to \"method\". Completely revised error handling . Revision 2020-04-23 Updated error handling, adding try-else-jump and changed how errors are passed. Included reflection page Revision 2020-03-30 Added Odin and D to comparisons. Updated text on how to contribute. Updated the example on undefined behaviour. Updated text on conversions. Moved double -> float conversion to \"ideas\" Fixed some typos. Revision 2020-03-29 Type inference for enums. Included macro page. Corrected precedence rules with try and @ . Type functions. Managed variables back to ideas. Volatile moved back to ideas. Removed implicit lossy signed conversions. Introducing safe signed-unsigned comparisons. \"Function block\" renamed \"expression block\". @ sigil removed from macros and is only used with macro invocations. Changed cast syntax from @cast(Type, var) to cast(var, Type) Revision 2019-12-26 Added module versioning system idea . Fleshed out polymorphic functions. Unsigned to signed promotion mentioned in \"changes from C\" Revision 2019-12-25 Changes how generic modules work. Switched so that vararrays use Type[*] and sub arrays use Type[] . Added submodule granularity, partial imports (only importing selected functions and types), removal of local , extended aliasing. See modules . Updated \"changes from C\" with removal of multiple declarations. Revision 2019-12-11 Updated the setup page. Revision 2019-12-03 Added page on conversions . Added page on undefined behaviour . Revision 2019-11-01 Updated \"changes from C\" with the lack of array decays. Added FourCC to the language Added name alias to ideas Added align asserts to ideas Added built in tests to ideas Added arrays page Added function blocks to statements page . Added expressions page . Added variables page . Moved managed pointers from idea to the variables page . Revision 2019-09-30 Removed references (non-nullable pointers) Removed idea with aliasing in import Revision 2019-08-14 Compile time run-include and include ideas. New module system idea. Revision 2019-08-14 Namespace separator changed to :: instead of . to simplify parsing. Added FourCC, Macro text interpolation to ideas. Added Yacc grammar (incomplete) Added \"attribute\" keyword. Changed type alias declaration to use typedef ... as ... . Introduced type operator. Added section about attributes. Revision 2019-08-02 Added error example. Added generics example. Added method function example. Added idea implicit method functions Expanded the types page somewhat. Revision 2019-07-30 Added default and named arguments to the functions page . Added varargs to the functions page . Added idea about hierarchal memory. Added idea of raw dynamic safe arrays & strings. Volatile sections are no longer prefixed by '@' Added idea regarding c3 interop Added page about c interop . Removed c_ichar and c_uchar types as they are redundant. Updates to keywords on the grammar page ../syntax). Revision 2019-07-27 Updated grammar with keywords. Added the docs & comments page. Updated the pre- and post-conditions . Revision 2019-07-24 Idea: typed varargs. Added \"pure\" post condition Updated c3c commands. Removed the type keyword for defining union/struct/enum/error. Revision 2019-07-23 Added to generic functions examples for [] and []= Developed ideas about vectors in the idea section . Defined 2's complement for signed integers. Idea: Managed pointers. Updated naming rules for types. Added more naming rules + examples of them. Removed \"defer on function signatures\" from ideas. Removed \"managed qualifier\" from ideas. Removed \"defer sugar\" from ideas. Removed \"built in dynamic arrays\" from ideas. Added standard_library section. Added more about pre- and post-conditions . Revision 2019-07-22 Added \"Design Principles\" to the index page. Revision 2019-07-21 \"return\" rather than function name is used in post conditions. See Functions Added \"@include\" macro for textual includes. See Modules . Files to without module for single file compilations is now ok as a special case. See Modules Added cone style array idea to the idea section . Added idea about defer on error to the idea section . Added idea for aliasing generic structs in the import to the idea section . Added idea for changing automatic signed <-> unsigned conversion to the idea section . Added Changes from C and Statements sections. Removal of volatile . See Changes from C and Statements Removal of const See Changes from C","title":"Changes"},{"location":"changes/#change-log","text":"","title":"Change log"},{"location":"changes/#revision-2023-10-24","text":"Removal of $checks and @checked Additional chapter on vectors. Addition of $and / $or . Added is_eq and is_ordered type properties.","title":"Revision 2023-10-24"},{"location":"changes/#revision-2023-10-20","text":"protocol => interface Overall update to reflection docs. Updates to distinct.","title":"Revision 2023-10-20"},{"location":"changes/#revision-2023-10-05","text":"protocol Update to any*","title":"Revision 2023-10-05"},{"location":"changes/#revision-2023-10-03","text":"exec and $exec change of static initializer","title":"Revision 2023-10-03"},{"location":"changes/#revision-2023-09-09","text":"Removal of implicit void! -> anyfault conversion. Change @catchof to @catch .","title":"Revision 2023-09-09"},{"location":"changes/#revision-2023-07-22","text":"Feature list. Macro ref parameter update. Abbreviated method first arg.","title":"Revision 2023-07-22"},{"location":"changes/#revision-2023-07-09","text":"Update with $embed information.","title":"Revision 2023-07-09"},{"location":"changes/#revision-2023-07-07","text":"Updated generics syntax and uses. Moved all \"ideas\" to the issue tracker.","title":"Revision 2023-07-07"},{"location":"changes/#revision-2023-06-23","text":"Distinct inline explanation. Module sections.","title":"Revision 2023-06-23"},{"location":"changes/#revision-2023-06-11","text":"Updated list of attributes. Better @if examples.","title":"Revision 2023-06-11"},{"location":"changes/#revision-2023-06-10","text":"Updated with @if directive.","title":"Revision 2023-06-10"},{"location":"changes/#revision-2023-06-03","text":"Added specific compiletime page. Updated examples with references. variant -> any,","title":"Revision 2023-06-03"},{"location":"changes/#revision-2023-06-05","text":"Chapter on dynamic code. Updated some pages with references to dynamic calls.","title":"Revision 2023-06-05"},{"location":"changes/#revision-2023-06-02","text":"Suffix '?' replaced by '!' and vice versa. Updates to ct statements. Fixes to array descriptions Replace try? / catch? with macros Updates to reflection define/typedef is now \"def\"","title":"Revision 2023-06-02"},{"location":"changes/#revision-2023-03-18","text":"try? / catch? println -> printn","title":"Revision 2023-03-18"},{"location":"changes/#revision-2023-02-15","text":"Added uint128 / int128 to the documentation. @private, @public and @local","title":"Revision 2023-02-15"},{"location":"changes/#revision-2023-02-14","text":"Updated typedef / define","title":"Revision 2023-02-14"},{"location":"changes/#revision-2023-02-11","text":"Changed @extname to @extern following compiler changes. Document @export","title":"Revision 2023-02-11"},{"location":"changes/#revision-2023-01-24","text":"Consistently use printfn rather than printfln Added short function syntax. Added lambdas .","title":"Revision 2023-01-24"},{"location":"changes/#revision-2023-01-07","text":"Direct download links added. Prefer \"String\" over char[]","title":"Revision 2023-01-07"},{"location":"changes/#revision-2022-12-28","text":"Bitstruct documented.","title":"Revision 2022-12-28"},{"location":"changes/#revision-2022-11-18","text":"Documented inline asm.","title":"Revision 2022-11-18"},{"location":"changes/#revision-2022-11-16","text":"Move description of static initialize. Info on operator overload. More details on function pointers. Updated project tree.","title":"Revision 2022-11-16"},{"location":"changes/#revision-2022-11-07","text":"Added information on enum associated values.","title":"Revision 2022-11-07"},{"location":"changes/#revision-2022-10-31","text":"Updated project.json description. Added information about static initializers and finalizers.","title":"Revision 2022-10-31"},{"location":"changes/#revision-2022-10-14","text":"libc::printf replaced with io::printf","title":"Revision 2022-10-14"},{"location":"changes/#revision-2022-10-01","text":"Expanded and updated types .","title":"Revision 2022-10-01"},{"location":"changes/#revision-2022-07-20","text":"Added start + len syntax","title":"Revision 2022-07-20"},{"location":"changes/#revision-2022-07-15","text":"Added \"builtins\"","title":"Revision 2022-07-15"},{"location":"changes/#revision-2022-06-30","text":"Updates to module system","title":"Revision 2022-06-30"},{"location":"changes/#revision-2022-04-05","text":"Removal of @opaque .","title":"Revision 2022-04-05"},{"location":"changes/#revision-2022-03-26","text":"Remove escape macros. Adding stringify and first class names. Removing \"?? jump\"","title":"Revision 2022-03-26"},{"location":"changes/#revision-2022-03-15","text":"Removal of multi-line strings","title":"Revision 2022-03-15"},{"location":"changes/#revision-2022-03-04","text":"Updates to $sizeof. Addition of $eval / $evaltype. Removal of $unreachable.","title":"Revision 2022-03-04"},{"location":"changes/#revision-2022-02-16","text":"Updates to imports. Updates to project files.","title":"Revision 2022-02-16"},{"location":"changes/#revision-2022-02-09","text":"Major revision to bring everything up to date.","title":"Revision 2022-02-09"},{"location":"changes/#revision-2021-10-20","text":"func replaced by fn Compound literal now Type { ... } like C++. Update of conversion rules New error syntax","title":"Revision 2021-10-20"},{"location":"changes/#revision-2021-08-27","text":"Updated reflection functionality. Added documentation for multi-line strings. Added documentation for base64 and hex array literals.","title":"Revision 2021-08-27"},{"location":"changes/#revision-2021-08-12","text":"Updated error type and error handling with try/catch","title":"Revision 2021-08-12"},{"location":"changes/#revision-2021-07-13","text":"Added nesting to / ... / removed /+ ... +/ Added primer.","title":"Revision 2021-07-13"},{"location":"changes/#revision-2021-06-20","text":"Updated array layout. Revised macros for foreach. Removed old generic functions. Added new ideas around generic, macros Changed macro body definition syntax. Introduced both $for and $foreach.","title":"Revision 2021-06-20"},{"location":"changes/#revision-2021-05-31","text":"Removal of vararray type. Updated user defined attributes. Removed incremental arrays. Added information on define . Added private modules and import.","title":"Revision 2021-05-31"},{"location":"changes/#revision-2021-05-18","text":"Change cast to (type)(expression)","title":"Revision 2021-05-18"},{"location":"changes/#revision-2021-05-08","text":"Added rationale for some changes from C. Updated undefined and undefined behaviour . Removed many of the fine-grained module features. Removed \"local\" visibility in modules . All modules are now distinct, parent modules do not have any special access to submodules. Added as module imports.","title":"Revision 2021-05-08"},{"location":"changes/#revision-2021-04-05","text":"\"next\" is now \"nextcase\". Added link to the C3 discord. The conversions page updated with new conversion rules. Updated compound literal syntax. Removed undefined behaviour behaviour on integer overflow and added a list of unspecified behaviour.","title":"Revision 2021-04-05"},{"location":"changes/#revision-2020-12-23","text":"Updated slice behaviour. Updated expression block syntax. Added link to specification-in-progress.","title":"Revision 2020-12-23"},{"location":"changes/#revision-2020-12-04","text":"Local variables are implicitly zero. Removed in-block declarations. Changed struct member initialization syntax. Changed named parameter syntax. Updated on macro syntax. Removed built in c types.","title":"Revision 2020-12-04"},{"location":"changes/#revision-2020-08-22","text":"Added slice operations. Changed cast syntax to cast( as ) .","title":"Revision 2020-08-22"},{"location":"changes/#revision-2020-07-08","text":"Additions to error handling . Introduction of labelled nextcase , break and continue . Removal of goto .","title":"Revision 2020-07-08"},{"location":"changes/#revision-2020-06-17","text":"Alternate casts in idea . Method functions simply renamed to \"method\". Completely revised error handling .","title":"Revision 2020-06-17"},{"location":"changes/#revision-2020-04-23","text":"Updated error handling, adding try-else-jump and changed how errors are passed. Included reflection page","title":"Revision 2020-04-23"},{"location":"changes/#revision-2020-03-30","text":"Added Odin and D to comparisons. Updated text on how to contribute. Updated the example on undefined behaviour. Updated text on conversions. Moved double -> float conversion to \"ideas\" Fixed some typos.","title":"Revision 2020-03-30"},{"location":"changes/#revision-2020-03-29","text":"Type inference for enums. Included macro page. Corrected precedence rules with try and @ . Type functions. Managed variables back to ideas. Volatile moved back to ideas. Removed implicit lossy signed conversions. Introducing safe signed-unsigned comparisons. \"Function block\" renamed \"expression block\". @ sigil removed from macros and is only used with macro invocations. Changed cast syntax from @cast(Type, var) to cast(var, Type)","title":"Revision 2020-03-29"},{"location":"changes/#revision-2019-12-26","text":"Added module versioning system idea . Fleshed out polymorphic functions. Unsigned to signed promotion mentioned in \"changes from C\"","title":"Revision 2019-12-26"},{"location":"changes/#revision-2019-12-25","text":"Changes how generic modules work. Switched so that vararrays use Type[*] and sub arrays use Type[] . Added submodule granularity, partial imports (only importing selected functions and types), removal of local , extended aliasing. See modules . Updated \"changes from C\" with removal of multiple declarations.","title":"Revision 2019-12-25"},{"location":"changes/#revision-2019-12-11","text":"Updated the setup page.","title":"Revision 2019-12-11"},{"location":"changes/#revision-2019-12-03","text":"Added page on conversions . Added page on undefined behaviour .","title":"Revision 2019-12-03"},{"location":"changes/#revision-2019-11-01","text":"Updated \"changes from C\" with the lack of array decays. Added FourCC to the language Added name alias to ideas Added align asserts to ideas Added built in tests to ideas Added arrays page Added function blocks to statements page . Added expressions page . Added variables page . Moved managed pointers from idea to the variables page .","title":"Revision 2019-11-01"},{"location":"changes/#revision-2019-09-30","text":"Removed references (non-nullable pointers) Removed idea with aliasing in import","title":"Revision 2019-09-30"},{"location":"changes/#revision-2019-08-14","text":"Compile time run-include and include ideas. New module system idea.","title":"Revision 2019-08-14"},{"location":"changes/#revision-2019-08-14_1","text":"Namespace separator changed to :: instead of . to simplify parsing. Added FourCC, Macro text interpolation to ideas. Added Yacc grammar (incomplete) Added \"attribute\" keyword. Changed type alias declaration to use typedef ... as ... . Introduced type operator. Added section about attributes.","title":"Revision 2019-08-14"},{"location":"changes/#revision-2019-08-02","text":"Added error example. Added generics example. Added method function example. Added idea implicit method functions Expanded the types page somewhat.","title":"Revision 2019-08-02"},{"location":"changes/#revision-2019-07-30","text":"Added default and named arguments to the functions page . Added varargs to the functions page . Added idea about hierarchal memory. Added idea of raw dynamic safe arrays & strings. Volatile sections are no longer prefixed by '@' Added idea regarding c3 interop Added page about c interop . Removed c_ichar and c_uchar types as they are redundant. Updates to keywords on the grammar page ../syntax).","title":"Revision 2019-07-30"},{"location":"changes/#revision-2019-07-27","text":"Updated grammar with keywords. Added the docs & comments page. Updated the pre- and post-conditions .","title":"Revision 2019-07-27"},{"location":"changes/#revision-2019-07-24","text":"Idea: typed varargs. Added \"pure\" post condition Updated c3c commands. Removed the type keyword for defining union/struct/enum/error.","title":"Revision 2019-07-24"},{"location":"changes/#revision-2019-07-23","text":"Added to generic functions examples for [] and []= Developed ideas about vectors in the idea section . Defined 2's complement for signed integers. Idea: Managed pointers. Updated naming rules for types. Added more naming rules + examples of them. Removed \"defer on function signatures\" from ideas. Removed \"managed qualifier\" from ideas. Removed \"defer sugar\" from ideas. Removed \"built in dynamic arrays\" from ideas. Added standard_library section. Added more about pre- and post-conditions .","title":"Revision 2019-07-23"},{"location":"changes/#revision-2019-07-22","text":"Added \"Design Principles\" to the index page.","title":"Revision 2019-07-22"},{"location":"changes/#revision-2019-07-21","text":"\"return\" rather than function name is used in post conditions. See Functions Added \"@include\" macro for textual includes. See Modules . Files to without module for single file compilations is now ok as a special case. See Modules Added cone style array idea to the idea section . Added idea about defer on error to the idea section . Added idea for aliasing generic structs in the import to the idea section . Added idea for changing automatic signed <-> unsigned conversion to the idea section . Added Changes from C and Statements sections. Removal of volatile . See Changes from C and Statements Removal of const See Changes from C","title":"Revision 2019-07-21"},{"location":"changesfromc/","text":"Changes from C Although C3 is trying to improve on C, this does not only mean addition of features, but also removal, or breaking changes: No mandatory header files There is a C3 interchange header format for declaring interfaces of libraries, but it is only used for special applications. Removal of the old C macro system The old C macro system is replaced by a new C3 macro system. Import and modules C3 uses module imports instead of header includes to link modules together. Member access using . even for pointers The -> operator is removed, access uses dot for both direct and pointer access. Note that this is just single access: to access a pointer of a pointer (e.g. int** ) an explicit dereference would be needed. Different operator precedence Notably bit operations have higher precedence than +/-, making code like this: a & b == c evaluate like (a & b) == c instead of C's a & (b == c) . See the page about precedence rules . Removal of the const type qualifier The const qualifier is only retained for actual constant variables. C3 uses a special type of post condition for functions to indicate that they do not alter in parameters. /** * This function ensures that foo is not changed in the function. * @param [in] foo * @param [out] bar **/ fn void test(Foo* foo, Bar* bar) { bar.y = foo.x; // foo.x = foo.x + 1 - compile time error, can't write to 'in' param. // int x = bar.y - compile time error, can't read from an 'out' param. } Rationale: const correctness requires littering const across the code base. Although const is useful, it provides weaker guarantees that it appears. Fixed arrays do not decay and have copy semantics C3 has three different array types. Variable arrays and slices decay to pointers, but fixed arrays are value objects and do not decay. int[3] a = { 1, 2, 3 }; int[4]* b = &a; // No conversion int* c = a; // ERROR int* d = &a; // Valid implicit conversion int* e = b; // Valid implicit conversion int[3] f = a; // Copy by value! Removal of multiple declaration syntax with initialization Only a single declaration with initialization is allowed per statement in C3: int i, j = 1; // ERROR int a = 1; // Ok int b, c; // Ok In conditionals, a special form of multiple declarations are allowed but each must then provide its type: for (int i = 0, int j = 1; i < 10; i++, j++) { ... } Integer promotions rules and safe signed-unsigned comparisons Promotion rules for integer types are different from C. C3 allows implicit widening only where there is only a single way to widen the expression. To explain the latter: take the case of long x = int_val_1 + int_val_2 . In C this would widen the result of the addition: long x = (long)(int_val_1 + int_val_2) , but there is another possible way to widen: long x = (long)int_val_1 + (long)int_val_2 . so in this case, the widening is disallowed. However, long x = int_val_1 is unambiguous, so C3 permits it just like C (read more on the conversion page . C3 also adds safe signed-unsigned comparisons : this means that comparing signed and unsigned values will always yield the correct result: // The code below would print \"Hello C3!\" in C3 and \"Hello C!\" in C. int i = -1; uint j = 1; if (i < j) { printf(\"Hello C3!\\n\"); } else { printf(\"Hello C!\\n\"); } Goto removed goto is removed and replaced with labelled break and continue together with the nextcase statement that allows you to jump between cases in a switch statement. Rationale: It is very difficult to make goto work well with defer and implicit unwrapping of optional results. It is not just making the compiler harder to write, but the code is harder to understand as well. The replacements together with defer cover many if not all usages of goto in regular code. Implicit break in switches Empty case statements have implicit fall through in C3, otherwise the nextcase statement is needed nextcase can also be used to jump to any other case statement in the switch. switch (h) { case 1: a = 1; nextcase; // Fall through case 2: b = 123; case 3: a = 2; nextcase 2; // Jump to case 2 default: a = 111; } Locals variables are implicitly zeroed In C global variables are implicitly zeroed out, but local variables aren't. In C3 local variables are zeroed out by default, but may be explicitly undefined 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 style: call_foo((Foo) { 1, 2, 3 }); // C++ style (1): call_foo(Foo(1, 2, 3)); // C++ style (2): call_foo(Foo { 1, 2, 3 }); // C3: call_foo(Foo { 1, 2, 3 } ); // C3 with inference: call_foo({ 1, 2, 3 }); Bitfields replaced by bitstructs Bitfields are replaced by bitstructs that have a well-defined encapsulating type, and an exact bit layout. // C struct Foo { int a : 3; unsigned b : 4; MyEnum c : 7; }; struct Flags { bool has_hyperdrive : 1; bool has_tractorbeam : 1; bool has_plasmatorpedoes : 1; } // C3 bitstruct Foo : short { int a : 0..2; uint b : 3..6; MyEnum c : 7..13; } // Simple form, only allowed when all fields are bools. struct Flags : char { bool has_hyperdrive; bool has_tractorbeam; bool has_plasmatorpedoes; } Evaluation order is well-defined Evaluation order is left-to-right, and in assignment expressions, assignment happens after expression evaluation. Signed overflow is well-defined Signed integer overflow always wraps using 2s complement. It is never undefined behaviour.","title":"Changes from C"},{"location":"changesfromc/#changes-from-c","text":"Although C3 is trying to improve on C, this does not only mean addition of features, but also removal, or breaking changes:","title":"Changes from C"},{"location":"changesfromc/#no-mandatory-header-files","text":"There is a C3 interchange header format for declaring interfaces of libraries, but it is only used for special applications.","title":"No mandatory header files"},{"location":"changesfromc/#removal-of-the-old-c-macro-system","text":"The old C macro system is replaced by a new C3 macro system.","title":"Removal of the old C macro system"},{"location":"changesfromc/#import-and-modules","text":"C3 uses module imports instead of header includes to link modules together.","title":"Import and modules"},{"location":"changesfromc/#member-access-using-even-for-pointers","text":"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.","title":"Member access using . even for pointers"},{"location":"changesfromc/#different-operator-precedence","text":"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 .","title":"Different operator precedence"},{"location":"changesfromc/#removal-of-the-const-type-qualifier","text":"The const qualifier is only retained for actual constant variables. C3 uses a special type of post condition for functions to indicate that they do not alter in parameters. /** * This function ensures that foo is not changed in the function. * @param [in] foo * @param [out] bar **/ fn void test(Foo* foo, Bar* bar) { bar.y = foo.x; // foo.x = foo.x + 1 - compile time error, can't write to 'in' param. // int x = bar.y - compile time error, can't read from an 'out' param. } Rationale: const correctness requires littering const across the code base. Although const is useful, it provides weaker guarantees that it appears.","title":"Removal of the const type qualifier"},{"location":"changesfromc/#fixed-arrays-do-not-decay-and-have-copy-semantics","text":"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!","title":"Fixed arrays do not decay and have copy semantics"},{"location":"changesfromc/#removal-of-multiple-declaration-syntax-with-initialization","text":"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++) { ... }","title":"Removal of multiple declaration syntax with initialization"},{"location":"changesfromc/#integer-promotions-rules-and-safe-signed-unsigned-comparisons","text":"Promotion rules for integer types are different from C. C3 allows implicit widening only where there is only a single way to widen the expression. To explain the latter: take the case of long x = int_val_1 + int_val_2 . In C this would widen the result of the addition: long x = (long)(int_val_1 + int_val_2) , but there is another possible way to widen: long x = (long)int_val_1 + (long)int_val_2 . so in this case, the widening is disallowed. However, long x = int_val_1 is unambiguous, so C3 permits it just like C (read more on the conversion page . C3 also adds safe signed-unsigned comparisons : this means that comparing signed and unsigned values will always yield the correct result: // The code below would print \"Hello C3!\" in C3 and \"Hello C!\" in C. int i = -1; uint j = 1; if (i < j) { printf(\"Hello C3!\\n\"); } else { printf(\"Hello C!\\n\"); }","title":"Integer promotions rules and safe signed-unsigned comparisons"},{"location":"changesfromc/#goto-removed","text":"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.","title":"Goto removed"},{"location":"changesfromc/#implicit-break-in-switches","text":"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; }","title":"Implicit break in switches"},{"location":"changesfromc/#locals-variables-are-implicitly-zeroed","text":"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.","title":"Locals variables are implicitly zeroed"},{"location":"changesfromc/#compound-literal-syntax-changed","text":"// 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 });","title":"Compound literal syntax changed"},{"location":"changesfromc/#bitfields-replaced-by-bitstructs","text":"Bitfields are replaced by bitstructs that have a well-defined encapsulating type, and an exact bit layout. // C struct Foo { int a : 3; unsigned b : 4; MyEnum c : 7; }; struct Flags { bool has_hyperdrive : 1; bool has_tractorbeam : 1; bool has_plasmatorpedoes : 1; } // C3 bitstruct Foo : short { int a : 0..2; uint b : 3..6; MyEnum c : 7..13; } // Simple form, only allowed when all fields are bools. struct Flags : char { bool has_hyperdrive; bool has_tractorbeam; bool has_plasmatorpedoes; }","title":"Bitfields replaced by bitstructs"},{"location":"changesfromc/#evaluation-order-is-well-defined","text":"Evaluation order is left-to-right, and in assignment expressions, assignment happens after expression evaluation.","title":"Evaluation order is well-defined"},{"location":"changesfromc/#signed-overflow-is-well-defined","text":"Signed integer overflow always wraps using 2s complement. It is never undefined behaviour.","title":"Signed overflow is well-defined"},{"location":"cinterop/","text":"C Interoperability 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.","title":"C Interop"},{"location":"cinterop/#c-interoperability","text":"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)); }","title":"C Interoperability"},{"location":"cinterop/#linking-static-and-dynamic-libraries","text":"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/\"] )","title":"Linking static and dynamic libraries"},{"location":"cinterop/#gotchas","text":"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.","title":"Gotchas"},{"location":"comments/","text":"Comments and docs C3 uses three distinct comment types: The normal // line comment, which is terminated at the end of the line. The classic /* ... */ C style comment, but unlike in C they are allowed to nest. Documentation comments /** ... **/ the text within these comments will be parsed as documentation and optional contracts on the following code. 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 for information regarding @require , @ensure , @const , @pure , @checked .","title":"Comments & Docs"},{"location":"comments/#comments-and-docs","text":"C3 uses three distinct comment types: The normal // line comment, which is terminated at the end of the line. The classic /* ... */ C style comment, but unlike in C they are allowed to nest. Documentation comments /** ... **/ the text within these comments will be parsed as documentation and optional contracts on the following code.","title":"Comments and docs"},{"location":"comments/#documentation","text":"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.","title":"Documentation"},{"location":"comments/#available-annotations","text":"Name format @param @param @return @return @return! @return! , , ... @fails @fails @deprecated @deprecated @require @require , , ... @ensure @ensure , , ... @pure @pure See contracts for information regarding @require , @ensure , @const , @pure , @checked .","title":"Available annotations"},{"location":"compare/","text":"Language comparisons 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 \u2013 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 \u2013 like an implicit context \u2013 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.","title":"Comparisons with other languages"},{"location":"compare/#language-comparisons","text":"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.","title":"Language comparisons"},{"location":"compare/#c","text":"As C3 is an evolution of C, the languages are quite similar. C3 adds features, but also removes a few.","title":"C"},{"location":"compare/#in-c3-but-not-in-c","text":"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","title":"In C3 but not in C"},{"location":"compare/#in-c-but-not-in-c3","text":"Qualified types ( const , volatile etc) Unsafe implicit conversions","title":"In C but not in C3"},{"location":"compare/#c_1","text":"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 \u2013 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.","title":"C++"},{"location":"compare/#in-c-but-not-in-c3_1","text":"Objects and classes RAII Exceptions","title":"In C++ but not in C3"},{"location":"compare/#in-c3-but-not-in-c_1","text":"Module system (yet) Integrated build system Semantic macros Error handling Defer Associated enum data Built-in subarrays Dynamic calls","title":"In C3 but not in C++"},{"location":"compare/#rust","text":"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.","title":"Rust"},{"location":"compare/#in-rust-but-not-in-c3","text":"RAII Memory safety Safe union types with functions Different syntax from C Pattern matching Async built in","title":"In Rust but not in C3"},{"location":"compare/#in-c3-but-not-in-rust","text":"Same ease of programming as C Optional contracts Familiar C syntax and behaviour Dynamic calls","title":"In C3 but not in Rust"},{"location":"compare/#zig","text":"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.","title":"Zig"},{"location":"compare/#in-zig-but-not-in-c3","text":"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.","title":"In Zig but not in C3"},{"location":"compare/#in-c3-but-not-in-zig","text":"Module system. Integrated build system. C ABI compatibility by default. Optional contracts. Familiar C syntax and behaviour. Dynamic interfaces. Built in benchmarks.","title":"In C3 but not in Zig"},{"location":"compare/#jai","text":"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.","title":"Jai"},{"location":"compare/#in-jai-but-not-in-c3","text":"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.","title":"In Jai but not in C3"},{"location":"compare/#in-c3-but-not-in-jai","text":"Module system. Integrated build system. Optional contracts. Familiar C syntax and behaviour. Fairly small language. Dynamic interfaces.","title":"In C3 but not in Jai"},{"location":"compare/#odin","text":"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 \u2013 like an implicit context \u2013 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.","title":"Odin"},{"location":"compare/#in-odin-but-not-in-c3","text":"Different syntax and behaviour compared to C. Ad hoc parametric polymorphism. Tuple returns. A rich built in set of types.","title":"In Odin but not in C3"},{"location":"compare/#in-c3-but-not-in-odin","text":"Familiar C syntax and behaviour. Semantic macros. Value methods. Optional contracts. Error handling support. Dynamic interfaces.","title":"In C3 but not in Odin"},{"location":"compare/#d","text":"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.","title":"D"},{"location":"compare/#in-d-but-not-in-c3","text":"Objects and classes. RAII. Exceptions. Optional GC. + Many, many more features.","title":"In D but not in C3"},{"location":"compare/#in-c3-but-not-in-d","text":"Fairly small language.","title":"In C3 but not in D"},{"location":"compiletime/","text":"Compile time introspection and execution 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: Non-conditional declarations are registered. Conditional module sections are either discarded or have all of their non-conditional declarations registered. Each module in turn will evaluate @if attributes for each module section. The order of module and module section evaluation in (2) and (3) is not deterministic and any use of $defined should not rely on this ordering. Compile time introspection At compile time, full type information is available. This allows for creation of reusable, code generating, macros for things like serialization. usz foo_alignment = Foo.alignof; usz foo_member_count = Foo.membersof.len; String foo_name = Foo.nameof; To read more about all the fields available at compile time, see the page on reflection .","title":"Compile time evaluation"},{"location":"compiletime/#compile-time-introspection-and-execution","text":"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.","title":"Compile time introspection and execution"},{"location":"compiletime/#compile-time-values","text":"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.","title":"Compile time values"},{"location":"compiletime/#if-and-switch","text":"$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 }","title":"$if and $switch"},{"location":"compiletime/#loops-using-foreach-and-for","text":"$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.","title":"Loops using $foreach and $for"},{"location":"compiletime/#compile-time-macro-execution","text":"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.","title":"Compile time macro execution"},{"location":"compiletime/#conditional-compilation-at-the-top-level-using-if","text":"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","title":"Conditional compilation at the top level using @if"},{"location":"compiletime/#evaluation-order-of-top-level-conditional-compilation","text":"Conditional compilation at the top level can cause unexpected ordering issues, especially when combined with $defined . At a high level, there are three phases of evaluation: Non-conditional declarations are registered. Conditional module sections are either discarded or have all of their non-conditional declarations registered. Each module in turn will evaluate @if attributes for each module section. The order of module and module section evaluation in (2) and (3) is not deterministic and any use of $defined should not rely on this ordering.","title":"Evaluation order of top level conditional compilation"},{"location":"compiletime/#compile-time-introspection","text":"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 .","title":"Compile time introspection"},{"location":"contracts/","text":"Contracts 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 \u2013 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(); }","title":"Contracts"},{"location":"contracts/#contracts","text":"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 \u2013 even if a potential bug may cause them to be violated.","title":"Contracts"},{"location":"contracts/#pre-conditions","text":"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; }","title":"Pre-conditions"},{"location":"contracts/#post-conditions","text":"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); }","title":"Post conditions"},{"location":"contracts/#parameter-annotations","text":"@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 }","title":"Parameter annotations"},{"location":"contracts/#pure-in-detail","text":"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.","title":"Pure in detail"},{"location":"contracts/#pre-conditions-for-macros","text":"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(); }","title":"Pre conditions for macros"},{"location":"contribute/","text":"The C3 language is still in its design phase , which means functionality and specification are subject to change. That also means that any contribution right now will have a big impact on the language. So if you find the project interesting, here's what you can do to help: Discuss the language on the C3 Discord https://discord.gg/qN76R87 Suggest improvements by filing an issue: https://github.com/c3lang/c3docs/issues/new Offer to work on the compiler being written here: https://github.com/c3lang/c3c","title":"Contribute"},{"location":"conversion/","text":"Conversions and promotions 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: For any floating point type with a bitwidth smaller than 32 bits, widen to float . E.g. f16 -> float For an integer type smaller than the minimum arithmetic width promote the value to a same signed integer of the minimum arithmetic width (this usually corresponds to a c int/uint). E.g. ushort -> uint Implicit narrowing An expression with an integer type, may implicitly narrow to smaller integer type, and similarly a float type may implicitly narrow to less wide floating point type is determined from the following algorithm. Shifts and assign look at the lhs expression. ++ , -- , ~ , - , !! , ! - check the inner type. + , - , * , / , % , ^ , | , & , ?? , ?: - check both lhs and rhs. Narrowing int/float cast, assume the type is the narrowed type. Widening int/float cast, look at the inner expression, ignoring the cast. In the case of any other cast, assume it is opaque and the type is that of the cast. In the case of an integer literal, instead of looking at the type, check that the integer would fit the type to narrow to. For .len access, allow narrowing to C int width. For all other expressions, check against the size of the type. As rough guide: if all the sub expressions originally are small enough it's ok to implicitly convert the result. Examples float16 h = 12.0; float f = 13.0; double d = 22.0; char x = 1; short y = -3; int z = 0xFFFFF; ulong w = -0xFFFFFFF; x = x + x; // => calculated as x = (char)((int)x + (int)x); x = y + x; // => Error, narrowing not allowed as y > char h = x * h; // => calculated as h = (float16)((float)x * (float)h); h = f + x; // => Error, narrowing not allowed since f > f16 Implicit widening Unlike C, implicit widening will only happen on \"simple expressions\": if the expression is a primary expression, or a unary operation on a primary expression. For assignment, special rules hold. For an assignment to a binary expression, if its two subexpressions are \"simple expressions\" and the binary expression is + , - , / , * , allow an implicit promotion of the two sub expressions. int a = ... short b = ... char c = ... long d = a; // Valid - simple expression. int e = (int)(d + (a + b)); // Error int f = (int)(d + ~b); // Valid long g = a + b; // Valid As a rule of thumb, if there are more than one possible conversion an explicit cast is needed. Example: long h = a + (b + c); // Possible intention 1 long h = (long)(a + (b + c)); // Possible intention 2 long h = (long)a + (long)(b + c); // Possible intention 3 long h = (long)a + ((long)b + (long)c); Maximum type The maximum type is a concept used when unifying two or more types. The algorithm follows: First perform implicit promotion. If both types are the same, the maximum type is this type. If one type is a floating point type, and the other is an integer type, the maximum type is the floating point type. E.g. int + float -> float . If both types are floating point types, the maximum type is the widest floating point type. E.g. float + double -> double . If both types are integer types with the same signedness, the maximum type is the widest integer type of the two. E.g. uint + ulong -> ulong . If both types are integer types with different signedness, the maximum type is a signed integer with the same bit width as the maximum integer type. ulong + int -> long If at least one side is a struct or a pointer to a struct with an inline directive on a member, check recursively check if the type of the inline member can be used to find a maximum type (see below under sub struct conversions) All other cases are errors. Substruct conversions Substructs may be used in place of its parent structs in many cases. The rule is as follows: A substruct pointer may implicitly convert to a parent struct. A substruct value may be implicitly assigned to a variable with the parent struct type, This will truncate the value, copying only the parent part of the substruct. However, a substruct value cannot be assigned its parent struct. Substruct 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 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. Resolve the operands. Find the maximum type of the two operands. Promote both operands to the resulting type if both are simple expressions The resulting type of the expression is the resulting type. 2. Addition with left side being a pointer Resolve the operands. If the rhs is not an integer, this is an error. If the rhs has a bit width that exceeds isz, this is an error. The result of the expression is the lhs type. 3. Subtraction with lhs pointer and rhs integer Resolve the operands. If the right hand type has a bit width that exceeds isz, this is an error. The result of the expression is the left hand type. 4. Subtraction with both sides pointers Resolve the operands. If the either side is a void * , it is cast to the other type. If the types of the sides are different, this is an error. The result of the expression is isz. If this result exceeds the target width, this is an error. 6. Bit operations ^ & | These operations are only valid for integers and booleans. Resolve the operands. Find the maximum type of the two operands. Promote both operands to the maximum type if they are simple expressions. The result of the expression is the maximum type. 6. Shift operations << >> These operations are only valid for integers. Resolve the operands. In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. 3The result of the expression is the lhs type. 7. Assignment operations += -= *= *= /= %= ^= |= &= Resolve the lhs. Resolve the right operand as an assignment rhs. The result of the expression is the lhs type. 8. Assignment shift >>= <<= Resolve both operands In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. The result of the expression is the lhs type. 9. && and || Resolve both operands. Insert bool cast of both operands. The type is bool. 10. <= == >= != Resolve the operands, left to right. Find the maximum type of the two operands. Promote both operands to the maximum type. The type is bool. Unary conversions 1. Bit negate Resolve the inner operand. If the inner type is not an integer this is an error. The type is the inner type. 2. Boolean not Resolve the inner operand. The type is bool. 3. Negation Resolve the inner operand. If the type inner type is not a number this is an error. If the inner type is an unsigned integer, cast it to the same signed type. The type is the type of the result from (3) 4. & and && Resolve the inner operand. The type is a pointer to the type of the inner operand. 5. * Resolve the inner operand. If the operand is not a pointer, or is a void * pointer, this is an error. The type is the pointee of the inner operand's type. Dereferencing 0 is implementation defined. 6. ++ and -- Resolve the inner operand. If the type is not a number, this is an error. The type is the same as the inner operand. Base expressions 1. Typed identifiers The type is that of the declaration. If the width of the type is less than that of the target type, widen to the target type. If the width of the type is greater than that of the target type, it is an error. 2. Constants and literals If the constant is an integer, it is assumed to be the arithmetic promotion width and signed. If the suffix u is added, it is assumed to be an unsigned number. If a suffix ixx or uxx is given then it is considered a an integer of that type width and signedness. It cannot be implicitly narrowed. If the constant is a floating point value, it is assumed to be a double unless suffixed 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.","title":"Conversions"},{"location":"conversion/#conversions-and-promotions","text":"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.","title":"Conversions and promotions"},{"location":"conversion/#target-type","text":"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.","title":"Target type"},{"location":"conversion/#common-arithmetic-promotion","text":"Like C, C3 uses implicit arithmetic promotion of integer and floating point variables before arithmetic operations: For any floating point type with a bitwidth smaller than 32 bits, widen to float . E.g. f16 -> float For an integer type smaller than the minimum arithmetic width promote the value to a same signed integer of the minimum arithmetic width (this usually corresponds to a c int/uint). E.g. ushort -> uint","title":"Common arithmetic promotion"},{"location":"conversion/#implicit-narrowing","text":"An expression with an integer type, may implicitly narrow to smaller integer type, and similarly a float type may implicitly narrow to less wide floating point type is determined from the following algorithm. Shifts and assign look at the lhs expression. ++ , -- , ~ , - , !! , ! - check the inner type. + , - , * , / , % , ^ , | , & , ?? , ?: - check both lhs and rhs. Narrowing int/float cast, assume the type is the narrowed type. Widening int/float cast, look at the inner expression, ignoring the cast. In the case of any other cast, assume it is opaque and the type is that of the cast. In the case of an integer literal, instead of looking at the type, check that the integer would fit the type to narrow to. For .len access, allow narrowing to C int width. For all other expressions, check against the size of the type. As rough guide: if all the sub expressions originally are small enough it's ok to implicitly convert the result. Examples float16 h = 12.0; float f = 13.0; double d = 22.0; char x = 1; short y = -3; int z = 0xFFFFF; ulong w = -0xFFFFFFF; x = x + x; // => calculated as x = (char)((int)x + (int)x); x = y + x; // => Error, narrowing not allowed as y > char h = x * h; // => calculated as h = (float16)((float)x * (float)h); h = f + x; // => Error, narrowing not allowed since f > f16","title":"Implicit narrowing"},{"location":"conversion/#implicit-widening","text":"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);","title":"Implicit widening"},{"location":"conversion/#maximum-type","text":"The maximum type is a concept used when unifying two or more types. The algorithm follows: First perform implicit promotion. If both types are the same, the maximum type is this type. If one type is a floating point type, and the other is an integer type, the maximum type is the floating point type. E.g. int + float -> float . If both types are floating point types, the maximum type is the widest floating point type. E.g. float + double -> double . If both types are integer types with the same signedness, the maximum type is the widest integer type of the two. E.g. uint + ulong -> ulong . If both types are integer types with different signedness, the maximum type is a signed integer with the same bit width as the maximum integer type. ulong + int -> long If at least one side is a struct or a pointer to a struct with an inline directive on a member, check recursively check if the type of the inline member can be used to find a maximum type (see below under sub struct conversions) All other cases are errors.","title":"Maximum type"},{"location":"conversion/#substruct-conversions","text":"Substructs may be used in place of its parent structs in many cases. The rule is as follows: A substruct pointer may implicitly convert to a parent struct. A substruct value may be implicitly assigned to a variable with the parent struct type, This will truncate the value, copying only the parent part of the substruct. However, a substruct value cannot be assigned its parent struct. Substruct subarrays, vararrays and arrays can not be cast (implicitly or explicitly) to an array of the parent struct type.","title":"Substruct conversions"},{"location":"conversion/#pointer-conversions","text":"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","title":"Pointer conversions"},{"location":"conversion/#vector-conversions","text":"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] .","title":"Vector conversions"},{"location":"conversion/#binary-conversions","text":"","title":"Binary conversions"},{"location":"conversion/#1-multiplication-division-remainder-subtraction-addition-with-both-operands-being-numbers","text":"These operations are only valid for integer and float types. Resolve the operands. Find the maximum type of the two operands. Promote both operands to the resulting type if both are simple expressions The resulting type of the expression is the resulting type.","title":"1. Multiplication, division, remainder, subtraction / addition with both operands being numbers"},{"location":"conversion/#2-addition-with-left-side-being-a-pointer","text":"Resolve the operands. If the rhs is not an integer, this is an error. If the rhs has a bit width that exceeds isz, this is an error. The result of the expression is the lhs type.","title":"2. Addition with left side being a pointer"},{"location":"conversion/#3-subtraction-with-lhs-pointer-and-rhs-integer","text":"Resolve the operands. If the right hand type has a bit width that exceeds isz, this is an error. The result of the expression is the left hand type.","title":"3. Subtraction with lhs pointer and rhs integer"},{"location":"conversion/#4-subtraction-with-both-sides-pointers","text":"Resolve the operands. If the either side is a void * , it is cast to the other type. If the types of the sides are different, this is an error. The result of the expression is isz. If this result exceeds the target width, this is an error.","title":"4. Subtraction with both sides pointers"},{"location":"conversion/#6-bit-operations","text":"These operations are only valid for integers and booleans. Resolve the operands. Find the maximum type of the two operands. Promote both operands to the maximum type if they are simple expressions. The result of the expression is the maximum type.","title":"6. Bit operations ^ & |"},{"location":"conversion/#6-shift-operations","text":"These operations are only valid for integers. Resolve the operands. In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. 3The result of the expression is the lhs type.","title":"6. Shift operations << >>"},{"location":"conversion/#7-assignment-operations-","text":"Resolve the lhs. Resolve the right operand as an assignment rhs. The result of the expression is the lhs type.","title":"7. Assignment operations += -= *= *= /= %= ^= |= &="},{"location":"conversion/#8-assignment-shift","text":"Resolve both operands In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. The result of the expression is the lhs type.","title":"8. Assignment shift >>= <<="},{"location":"conversion/#9-and","text":"Resolve both operands. Insert bool cast of both operands. The type is bool.","title":"9. && and ||"},{"location":"conversion/#10","text":"Resolve the operands, left to right. Find the maximum type of the two operands. Promote both operands to the maximum type. The type is bool.","title":"10. <= == >= !="},{"location":"conversion/#unary-conversions","text":"","title":"Unary conversions"},{"location":"conversion/#1-bit-negate","text":"Resolve the inner operand. If the inner type is not an integer this is an error. The type is the inner type.","title":"1. Bit negate"},{"location":"conversion/#2-boolean-not","text":"Resolve the inner operand. The type is bool.","title":"2. Boolean not"},{"location":"conversion/#3-negation","text":"Resolve the inner operand. If the type inner type is not a number this is an error. If the inner type is an unsigned integer, cast it to the same signed type. The type is the type of the result from (3)","title":"3. Negation"},{"location":"conversion/#4-and","text":"Resolve the inner operand. The type is a pointer to the type of the inner operand.","title":"4. & and &&"},{"location":"conversion/#5","text":"Resolve the inner operand. If the operand is not a pointer, or is a void * pointer, this is an error. The type is the pointee of the inner operand's type. Dereferencing 0 is implementation defined.","title":"5. *"},{"location":"conversion/#6-and-","text":"Resolve the inner operand. If the type is not a number, this is an error. The type is the same as the inner operand.","title":"6. ++ and --"},{"location":"conversion/#base-expressions","text":"","title":"Base expressions"},{"location":"conversion/#1-typed-identifiers","text":"The type is that of the declaration. If the width of the type is less than that of the target type, widen to the target type. If the width of the type is greater than that of the target type, it is an error.","title":"1. Typed identifiers"},{"location":"conversion/#2-constants-and-literals","text":"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. 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.","title":"2. Constants and literals"},{"location":"define/","text":"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 . 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 }","title":"Define"},{"location":"define/#the-def-statement","text":"The def statement in C3 is intended for aliasing identifiers and types.","title":"The \"def\" statement"},{"location":"define/#defining-a-type-alias","text":"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);","title":"Defining a type alias"},{"location":"define/#distinct-types","text":"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","title":"Distinct types"},{"location":"define/#distinct-inline","text":"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!","title":"Distinct inline"},{"location":"define/#function-and-variable-aliases","text":"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; }","title":"Function and variable aliases"},{"location":"define/#using-def-to-create-generic-types-functions-and-variables","text":"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 .","title":"Using def to create generic types, functions and variables"},{"location":"define/#function-pointer-default-arguments-and-named-parameters","text":"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 }","title":"Function pointer default arguments and named parameters"},{"location":"examples/","text":"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 . 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 . 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 . 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: 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 . 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 Dynamic calls Runtime dynamic dispatch through interfaces: import std::io; // Define a dynamic interface interface MyName { fn String myname(); } struct Bob (MyName) { int x; } // Required implementation as Bob implements MyName fn String Bob.myname(Bob*) @dynamic { return \"I am Bob!\"; } // Ad hoc implementation fn String int.myname(int*) @dynamic { return \"I am int!\"; } fn void whoareyou(any* a) { MyName* b = (MyName*)a; if (!&b.myname) { io::printn(\"I don't know who I am.\"); return; } io::printn(b.myname()); } fn void main() { int i = 1; double d = 1.0; Bob bob; any* a = &i; whoareyou(a); a = &d; whoareyou(a); a = &bob; whoareyou(a); } Read more about dynamic calls here .","title":"Examples"},{"location":"examples/#if-statement","text":"fn void if_example(int a) { if (a > 0) { // .. } else { // .. } }","title":"if-statement"},{"location":"examples/#for-loop","text":"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 (;;) { // .. } }","title":"for-loop"},{"location":"examples/#foreach-loop","text":"fn void example_foreach(float[] values) { foreach (index, value : values) { io::printfn(\"%d: %f\", index, value); } }","title":"foreach-loop"},{"location":"examples/#while-loop","text":"fn void example_while() { // again exactly the same as C int a = 10; while (a > 0) { a--; } // Declaration while (Point* p = getPoint()) { // .. } }","title":"while-loop"},{"location":"examples/#enum-switch","text":"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];","title":"enum + switch"},{"location":"examples/#defer","text":"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","title":"defer"},{"location":"examples/#struct-types","text":"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; } }","title":"struct types"},{"location":"examples/#function-pointers","text":"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); // .. }","title":"Function pointers"},{"location":"examples/#error-handling","text":"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 .","title":"Error handling"},{"location":"examples/#contracts","text":"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 .","title":"Contracts"},{"location":"examples/#macros","text":"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 .","title":"Macros"},{"location":"examples/#methods","text":"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); }","title":"Methods"},{"location":"examples/#compile-time-reflection-and-execution","text":"Access type information and loop over values at compile time: import std::io; struct Foo { int a; double b; int* ptr; } macro print_fields($Type) { $foreach ($field : $Type.membersof) io::printfn(\"Field %s, offset: %s, size: %s, type: %s\", $field.nameof, $field.offsetof, $field.sizeof, $field.typeid.nameof); $endforeach } fn void main() { print_fields(Foo); } This prints on x64: Field a, offset: 0, size: 4, type: int Field b, offset: 8, size: 8, type: double Field ptr, offset: 16, size: 8, type: int*","title":"Compile time reflection and execution"},{"location":"examples/#compile-time-execution","text":"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 .","title":"Compile time execution"},{"location":"examples/#generic-modules","text":"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","title":"Generic modules"},{"location":"examples/#dynamic-calls","text":"Runtime dynamic dispatch through interfaces: import std::io; // Define a dynamic interface interface MyName { fn String myname(); } struct Bob (MyName) { int x; } // Required implementation as Bob implements MyName fn String Bob.myname(Bob*) @dynamic { return \"I am Bob!\"; } // Ad hoc implementation fn String int.myname(int*) @dynamic { return \"I am int!\"; } fn void whoareyou(any* a) { MyName* b = (MyName*)a; if (!&b.myname) { io::printn(\"I don't know who I am.\"); return; } io::printn(b.myname()); } fn void main() { int i = 1; double d = 1.0; Bob bob; any* a = &i; whoareyou(a); a = &d; whoareyou(a); a = &bob; whoareyou(a); } Read more about dynamic calls here .","title":"Dynamic calls"},{"location":"expressions/","text":"Expressions Expressions work like in C, with one exception: it is possible to take the address of a temporary. This uses the operator && rather than & . Consequently, this is valid: fn void test(int* x) { ... } test(&&1); // In C: // int x = 1; // test(&x); Well-defined evaluation order Expressions have a well-defined evaluation order: Binary expressions are evaluated from left to right. Assignment occurs right to left, so a = a++ would result in a being unchanged. Call arguments are evaluated in parameter order. 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: The null literal. Boolean, floating point and integer literals. The result of arithmetics on constant expressions. Compile time variables (prefixed with $ ) Global constant variables with initializers that are constant expressions. The result of macros that does not generate code and only uses constant expressions. The result of a cast if the value is cast to a boolean, floating point or integer type and the value that is converted is a constant expression. String literals. Initializer lists containing constant values. Some things that are not constant expressions: Any pointer that isn't the null literal, even if it's derived from a constant expression. The result of a cast except for casts of constant expressions to a numeric type. Compound literals - even when values are constant expressions. Including binary data The $embed(...) function includes the contents of a file into the compilation as a constant array of bytes: char[*] my_image = $embed(\"my_image.png\"); The result of an embed work similar to a string literal and can implicitly convert to a char* , void* , char[] , char[*] and String . Limiting length It's possible to limit the length of included with the optional second parameter. char[4] my_data = $embed(\"foo.txt\", 4]; Failure to load at compile time and defaults Usually it's a compile time error if the file can't be included, but sometimes it's useful to only optionally include it. If this is desired, declare the left hand side 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;","title":"Expressions"},{"location":"expressions/#expressions","text":"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);","title":"Expressions"},{"location":"expressions/#well-defined-evaluation-order","text":"Expressions have a well-defined evaluation order: Binary expressions are evaluated from left to right. Assignment occurs right to left, so a = a++ would result in a being unchanged. Call arguments are evaluated in parameter order. 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 .","title":"Well-defined evaluation order"},{"location":"expressions/#compound-literals","text":"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 }));","title":"Compound literals"},{"location":"expressions/#constant-expressions","text":"In C3 all constant expressions are guaranteed to be calculated at compile time. The following are considered constant expressions: The null literal. Boolean, floating point and integer literals. The result of arithmetics on constant expressions. Compile time variables (prefixed with $ ) Global constant variables with initializers that are constant expressions. The result of macros that does not generate code and only uses constant expressions. The result of a cast if the value is cast to a boolean, floating point or integer type and the value that is converted is a constant expression. String literals. Initializer lists containing constant values. Some things that are not constant expressions: Any pointer that isn't the null literal, even if it's derived from a constant expression. The result of a cast except for casts of constant expressions to a numeric type. Compound literals - even when values are constant expressions.","title":"Constant expressions"},{"location":"expressions/#including-binary-data","text":"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 .","title":"Including binary data"},{"location":"expressions/#limiting-length","text":"It's possible to limit the length of included with the optional second parameter. char[4] my_data = $embed(\"foo.txt\", 4];","title":"Limiting length"},{"location":"expressions/#failure-to-load-at-compile-time-and-defaults","text":"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;","title":"Failure to load at compile time and defaults"},{"location":"faq/","text":"Frequently Asked Questions 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.","title":"FAQ"},{"location":"faq/#frequently-asked-questions","text":"","title":"Frequently Asked Questions"},{"location":"faq/#standard-library","text":"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.","title":"Standard library"},{"location":"faq/#language-features","text":"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;","title":"Language features"},{"location":"faq/#memory-management","text":"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; };","title":"Memory management"},{"location":"faq/#interfacing-with-c-code","text":"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.","title":"Interfacing with C code"},{"location":"faq/#patterns","text":"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) .","title":"Patterns"},{"location":"faq/#syntax-language-design","text":"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.","title":"Syntax & Language design"},{"location":"firstproject/","text":"Your First Project Starting out with C3, you probably want to get a feel for the language, without using the integrated build system. Open a text editor and enter the following in a file you call hello_world.c3 : module hello_world; import std::io; fn int main(String[] argv) { io::printn(\"Hello World!\"); return 0; } Now in the terminal type: $ c3c compile hello_world.c3 $ ./hello_world Hello World $ A real project Once you go beyond simple files, you want to create a real project. Do so by entering c3c init hello_world . You will get the following structure: $ c3c init hello_world $ tree . . \u2514\u2500\u2500 hello_world \u251c\u2500\u2500 project.json \u251c\u2500\u2500 LICENSE \u251c\u2500\u2500 README.md \u251c\u2500\u2500 build \u251c\u2500\u2500 docs \u251c\u2500\u2500 lib \u251c\u2500\u2500 resources \u251c\u2500\u2500 src \u2502 \u2514\u2500\u2500 main.c3 \u2514\u2500\u2500 test Enter main.c3 and write the same code as above, then anywhere in the project structure: $ c3c run Hello World $","title":"Your first project"},{"location":"firstproject/#your-first-project","text":"Starting out with C3, you probably want to get a feel for the language, without using the integrated build system. Open a text editor and enter the following in a file you call hello_world.c3 : module hello_world; import std::io; fn int main(String[] argv) { io::printn(\"Hello World!\"); return 0; } Now in the terminal type: $ c3c compile hello_world.c3 $ ./hello_world Hello World $","title":"Your First Project"},{"location":"firstproject/#a-real-project","text":"Once you go beyond simple files, you want to create a real project. Do so by entering c3c init hello_world . You will get the following structure: $ c3c init hello_world $ tree . . \u2514\u2500\u2500 hello_world \u251c\u2500\u2500 project.json \u251c\u2500\u2500 LICENSE \u251c\u2500\u2500 README.md \u251c\u2500\u2500 build \u251c\u2500\u2500 docs \u251c\u2500\u2500 lib \u251c\u2500\u2500 resources \u251c\u2500\u2500 src \u2502 \u2514\u2500\u2500 main.c3 \u2514\u2500\u2500 test Enter main.c3 and write the same code as above, then anywhere in the project structure: $ c3c run Hello World $","title":"A real project"},{"location":"functions/","text":"Functions 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: single typed explicitly typed any: pass non-any arguments as references implicitly typed any: arguments are implicitly converted to references (use with care) untyped C-style Examples: fn void va_singletyped(int... args) { /* args has type int[] */ } fn void va_variants_explicit(any*... args) { /* args has type any*[] */ } fn void va_variants_implicit(args...) { /* args has type any*[] */ } extern fn void va_untyped(...); // only used for extern C functions fn void test() { va_singletyped(1, 2, 3); int x = 1; any* v = &x; va_variants_explicit(&&1, &x, v); // pass references for non-any arguments va_variants_implicit(1, x, \"foo\"); // arguments are implicitly converted to anys va_untyped(1, x, \"foo\"); // extern C-function } Functions and optional returns The return parameter may be an optional result type \u2013 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 \"); }","title":"Functions"},{"location":"functions/#functions","text":"C3 has both regular functions and member functions. Member functions are functions namespaced using type names, and allows invocations using the dot syntax.","title":"Functions"},{"location":"functions/#regular-functions","text":"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); } }","title":"Regular functions"},{"location":"functions/#function-arguments","text":"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! }","title":"Function arguments"},{"location":"functions/#varargs","text":"There are four types of varargs: single typed explicitly typed any: pass non-any arguments as references implicitly typed any: arguments are implicitly converted to references (use with care) untyped C-style Examples: fn void va_singletyped(int... args) { /* args has type int[] */ } fn void va_variants_explicit(any*... args) { /* args has type any*[] */ } fn void va_variants_implicit(args...) { /* args has type any*[] */ } extern fn void va_untyped(...); // only used for extern C functions fn void test() { va_singletyped(1, 2, 3); int x = 1; any* v = &x; va_variants_explicit(&&1, &x, v); // pass references for non-any arguments va_variants_implicit(1, x, \"foo\"); // arguments are implicitly converted to anys va_untyped(1, x, \"foo\"); // extern C-function }","title":"Varargs"},{"location":"functions/#functions-and-optional-returns","text":"The return parameter may be an optional result type \u2013 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\"); }","title":"Functions and optional returns"},{"location":"functions/#methods","text":"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; } }","title":"Methods"},{"location":"functions/#implicit-first-parameters","text":"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.","title":"Implicit first parameters"},{"location":"functions/#restrictions-on-methods","text":"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.","title":"Restrictions on methods"},{"location":"functions/#contracts","text":"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.","title":"Contracts"},{"location":"functions/#short-function-declaration-syntax","text":"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;","title":"Short function declaration syntax"},{"location":"functions/#lambdas","text":"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); }","title":"Lambdas"},{"location":"functions/#static-initializer-and-finalizers","text":"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.","title":"Static initializer and finalizers"},{"location":"functions/#changing-priority-of-static-initializers-and-finalizers","text":"It is possible to provide an argument to the attributes to set the actual priority. It is recommended that programs use a priority of 1024 or higher. The higher the value, the later it will be called. The lowest priority is 65535. // Print \"Hello World\" at startup. fn void start_world @init(3000) { io::printn(\"World\"); } fn void start_hello @init(2000) { io::print(\"Hello \"); }","title":"Changing priority of static initializers and finalizers"},{"location":"generics/","text":"Generics 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.","title":"Generics"},{"location":"generics/#generics","text":"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.","title":"Generics"},{"location":"ideas/","text":"Ideas All ideas have moved to the issue tracker: https://github.com/c3lang/c3c/issues.","title":"More ideas"},{"location":"ideas/#ideas","text":"All ideas have moved to the issue tracker: https://github.com/c3lang/c3c/issues.","title":"Ideas"},{"location":"libraries/","text":"Libraries Note, the library system is in early alpha, everything below is subject to change C3 allows convenient packaging of C3 source files optionally with statically or dynamically linked libraries. To use such a library, simply pass the path to the library directory and add the library you wish to link to. The compiler will resolve any dependencies to other libraries and only compile those that are in use. How it works A library may be used either packaged or unpacked. If unpacked, it is simply a directory with the .c3l suffix, which contains all the necessary files, if packed, this is simply a compressed variant of a directory with the same structure. The specification In the top of the library resides the manifest.json file which has the following structure: { \"provides\" : \"my_lib\", \"execs\" : [], \"targets\" : { \"macos-x64\" : { \"linkflags\" : [], \"dependencies\" : [], \"linked-libs\" : [\"my_lib_static\", \"Cocoa.framework\", \"c\"] }, \"windows-x64\" : { \"linkflags\" : [\"/stack:65536\"], \"dependencies\" : [\"ms_my_extra\"], \"linked-libs\" : [\"my_lib_static\", \"kernel32\"], \"execs\" : [], } } } In the example here, this library supports two targets: macos-x64 and windows-x64 . If we tried to use it with any other target, the compiler would give an error. We see that if we use the windows-x64 target it will also load the ms_my_extra library. And we also see that the linker would have a special argument on that platform. Both targets expect my_lib_static to be available for linking. If this library provides this or dynamic library it will be in the target sub-directories, so it likely has the path windows-x64/my_lib_static.lib and macos-z64/libmy_lib_static.a . Source code Aside from the manifest, C3 will read any C and C3 files in the same directory as manifest.json as well as any files in the target subdirectory for the current target. For static libraries typically a .c3i file (that is, a C3 file without any implementations) is provided, similar to how .h files are used in C. Additional actions \"exec\" , which is available both at the top level and per-target, lists the scripts which will be invoked when a library is used. This requires running the compiler at full trust level using the --trust=full option. How to \u2013 automatically \u2013 export libraries This is not implemented yet, docs will materialize once it is finished","title":"Libraries"},{"location":"libraries/#libraries","text":"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.","title":"Libraries"},{"location":"libraries/#how-it-works","text":"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.","title":"How it works"},{"location":"libraries/#the-specification","text":"In the top of the library resides the manifest.json file which has the following structure: { \"provides\" : \"my_lib\", \"execs\" : [], \"targets\" : { \"macos-x64\" : { \"linkflags\" : [], \"dependencies\" : [], \"linked-libs\" : [\"my_lib_static\", \"Cocoa.framework\", \"c\"] }, \"windows-x64\" : { \"linkflags\" : [\"/stack:65536\"], \"dependencies\" : [\"ms_my_extra\"], \"linked-libs\" : [\"my_lib_static\", \"kernel32\"], \"execs\" : [], } } } In the example here, this library supports two targets: macos-x64 and windows-x64 . If we tried to use it with any other target, the compiler would give an error. We see that if we use the windows-x64 target it will also load the ms_my_extra library. And we also see that the linker would have a special argument on that platform. Both targets expect my_lib_static to be available for linking. If this library provides this or dynamic library it will be in the target sub-directories, so it likely has the path windows-x64/my_lib_static.lib and macos-z64/libmy_lib_static.a .","title":"The specification"},{"location":"libraries/#source-code","text":"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.","title":"Source code"},{"location":"libraries/#additional-actions","text":"\"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.","title":"Additional actions"},{"location":"libraries/#how-to-automatically-export-libraries","text":"This is not implemented yet, docs will materialize once it is finished","title":"How to \u2013 automatically \u2013 export libraries"},{"location":"macros/","text":"Macros The macro capabilities of C3 reaches across several constructs: macros (prefixed with @ at invocation), generic functions , 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 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 . 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 Macro directives","title":"Macros"},{"location":"macros/#macros","text":"The macro capabilities of C3 reaches across several constructs: macros (prefixed with @ at invocation), generic functions , generic modules , compile time variables (prefixed with $ ), macro compile time execution (using $if , $for , $foreach , $switch ) and attributes.","title":"Macros"},{"location":"macros/#a-quick-comparison-of-c-and-c3-macros","text":"","title":"A quick comparison of C and C3 macros"},{"location":"macros/#conditional-compilation","text":"// 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);","title":"Conditional compilation"},{"location":"macros/#macros_1","text":"// 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;","title":"Macros"},{"location":"macros/#dynamic-scoping","text":"// C #define Z() ptr->x->y->z int x = Z(); // C3 ... currently no corresponding functionality ...","title":"Dynamic scoping"},{"location":"macros/#reference-arguments","text":"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);","title":"Reference arguments"},{"location":"macros/#first-class-types","text":"// C #define SIZE(T) (sizeof(T) + sizeof(int)) // C3 macro size($Type) { return $Type.sizeof + int.sizeof; }","title":"First class types"},{"location":"macros/#trailing-blocks-for-macros","text":"// 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; }","title":"Trailing blocks for macros"},{"location":"macros/#first-class-names","text":"// C #define offsetof(T, field) (size_t)(&((T*)0)->field) // C3 macro usz offset($Type, #field) { $Type* t = null; return (usz)(uptr)&t.#field; }","title":"First class names"},{"location":"macros/#declaration-attributes","text":"// 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 { ... }","title":"Declaration attributes"},{"location":"macros/#declaration-macros","text":"// C #define DECLARE_LIST(name) List name = { .head = NULL }; // Use: DECLARE_LIST(hello) // C3 ... currently no corresponding functionality ...","title":"Declaration macros"},{"location":"macros/#stringification","text":"#define CHECK(x) do { if (!x) abort(#x); } while(0) // C3 macro fn check(#expr) { if (!#expr) abort($stringify(#expr)); }","title":"Stringification"},{"location":"macros/#top-level-evaluation","text":"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.","title":"Top level evaluation"},{"location":"macros/#macro-declarations","text":"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; } }","title":"Macro declarations"},{"location":"macros/#macro-methods","text":"Similar to regular methods a macro may also be associated with a particular type: struct Foo { ... } macro Foo.generate(&self) { ... } Foo f; f.generate(); See the chapter on functions for more details.","title":"Macro methods"},{"location":"macros/#capturing-a-trailing-block","text":"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); } } }","title":"Capturing a trailing block"},{"location":"macros/#macros-returning-values","text":"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); }","title":"Macros returning values"},{"location":"macros/#calling-macros","text":"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.","title":"Calling macros"},{"location":"macros/#macro-vaargs","text":"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","title":"Macro vaargs"},{"location":"macros/#vacount","text":"Returns the number of arguments.","title":"$vacount"},{"location":"macros/#vaarg","text":"Returns the argument as a regular parameter. The argument is guaranteed to be evaluated once, even if the argument is used multiple times.","title":"$vaarg"},{"location":"macros/#vaconst","text":"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.","title":"$vaconst"},{"location":"macros/#vaexpr","text":"Returns the argument as an unevaluated expression. Multiple uses will evaluate the expression multiple times, this corresponds to # parameters.","title":"$vaexpr"},{"location":"macros/#vatype","text":"Returns the argument as a type. This corresponds to $Type style parameters, e.g. $vatype(2) a = 2","title":"$vatype"},{"location":"macros/#varef","text":"Returns the argument as an lvalue. This corresponds to &myref style parameters, e.g. $varef(1) = 123 .","title":"$varef"},{"location":"macros/#untyped-lists","text":"Compile time variables may hold untyped lists. Such lists may be iterated over or implicitly converted to initializer lists: var $a = { 1, 2 }; $foreach ($x : $a) io::printfn(\"%d\", $x); $endforeach int[2] x = $a; io::printfn(\"%s\", x); io::printfn(\"%s\", $a[1]); // Will print // 1 // 2 // [1, 2] // 2","title":"Untyped lists"},{"location":"macros/#macro-directives","text":"","title":"Macro directives"},{"location":"modules/","text":"Modules 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: The std::core module (and sub modules). Any other module sharing the same top module. E.g. the module foo::abc will implicitly also import 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\u00b4s 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: Functions, macros, constants and variables require at least the (sub-) module name. Types do not require the module name unless the name is ambiguous. In case of ambiguity, only so many levels of module names are needed as to make the symbol unambiguous. // File a.c3 module a; struct Foo { ... } struct Bar { ... } struct TheAStruct { ... } fn void anAFunction() { ... } // File b.c3 module b; struct Foo { ... } struct Bar { ... } struct TheBStruct { ... } fn void aBFunction() { ... } // File c.c3 module c; import a, b; struct TheCStruct { ... } struct Bar { ... } fn void aCFunction() { ... } fn void test() { TheAStruct stA; TheBStruct stB; TheCStruct stC; // Name required to avoid ambiguity; b::Foo stBFoo; // Will always pick the current module's // name. Bar bar; // Namespace required: a::aAFunction(); b::aBFunction(); // A local symbol does not require it: aCFunction(); } This means that the rule for the common case can be summarized as Types are used without prefix; functions, variables, macros and constants are prefixed with the sub module name. Module sections A single file may have multiple module declarations, even for the same module. This allows us to write for example: // File foo.c3 module foo; fn int hello_world() { return my_hello_world(); } module foo @private; import std::io; // The import is only visible in this section. fn int my_hello_world() // @private by default { io::printn(\"Hello, world\\n\"); return 0; } module foo @test; fn void test_hello() // @test by default { assert(hello_world() == 0); } Versioning and dynamic inclusion NOTE: This feature may significantly change. When including dynamic libraries, it is possible to use optional functions and globals. This is done using the @dynamic attribute. An example library could have this: dynlib.c3i module dynlib; fn void do_something() @dynamic(4.0) fn void do_something_else() @dynamic(0, 5.0) fn void do_another_thing() @dynamic(0, 2.5) Importing the dynamic library and setting the base version to 4.5 and minimum version to 3.0, we get the following: test.c3 import dynlib; fn void test() { if (@available(dynlib::do_something)) { dynlib::do_something(); } else { dynlib::do_someting_else(); } } In this example the code would run do_something if available (that is, when the dynamic library is 4.0 or higher), or fallback to do_something_else otherwise. If we tried to conditionally add something not available in the compilation itself, that is a compile time error: if (@available(dynlib::do_another_thing)) { 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: import std::io; // On Linux or MacOS this will insert 'String a = \"Hello world!\";' $exec(\"echo\", \"String a = \\\\\\\"Hello world!\\\\\\\"\\\\;\"); fn void main() { io::printn(a); } Using $exec requires full trust level , which is enabled with -trust=full from the command line. '$exec' will by default run from the /scripts directory for projects, for non-project builds, the current directory is used as well. $exec scripting $exec allows a special scripting mode, where one or more C3 files are compiled on the fly and run by $exec . import std::io; // Compile foo.c3 and bar.c3 in the /scripts directory, invoke the resulting binary // with the argument 'test' $exec(\"foo.c3;bar.c3\", \"test\"); fn void main() { ... }","title":"Modules"},{"location":"modules/#modules","text":"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 .","title":"Modules"},{"location":"modules/#details","text":"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 .","title":"Details"},{"location":"modules/#importing-modules","text":"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 = {};","title":"Importing modules"},{"location":"modules/#implicit-imports","text":"The module system will also implicitly import: The std::core module (and sub modules). Any other module sharing the same top module. E.g. the module foo::abc will implicitly also import modules foo and foo::cde if they exist.","title":"Implicit imports"},{"location":"modules/#visibility","text":"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 }","title":"Visibility"},{"location":"modules/#overriding-symbol-visibility-rules","text":"By using import @public , it's possible to access another module\u00b4s 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.","title":"Overriding symbol visibility rules"},{"location":"modules/#changing-the-default-visibility","text":"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","title":"Changing the default visibility"},{"location":"modules/#linker-visibility-and-exports","text":"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.","title":"Linker visibility and exports"},{"location":"modules/#using-functions-and-types-from-other-modules","text":"As a rule, functions, macros, constants, variables and types in the same module do not need any namespace prefix. For imported modules the following rules hold: Functions, macros, constants and variables require at least the (sub-) module name. Types do not require the module name unless the name is ambiguous. In case of ambiguity, only so many levels of module names are needed as to make the symbol unambiguous. // File a.c3 module a; struct Foo { ... } struct Bar { ... } struct TheAStruct { ... } fn void anAFunction() { ... } // File b.c3 module b; struct Foo { ... } struct Bar { ... } struct TheBStruct { ... } fn void aBFunction() { ... } // File c.c3 module c; import a, b; struct TheCStruct { ... } struct Bar { ... } fn void aCFunction() { ... } fn void test() { TheAStruct stA; TheBStruct stB; TheCStruct stC; // Name required to avoid ambiguity; b::Foo stBFoo; // Will always pick the current module's // name. Bar bar; // Namespace required: a::aAFunction(); b::aBFunction(); // A local symbol does not require it: aCFunction(); } This means that the rule for the common case can be summarized as Types are used without prefix; functions, variables, macros and constants are prefixed with the sub module name.","title":"Using functions and types from other modules"},{"location":"modules/#module-sections","text":"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); }","title":"Module sections"},{"location":"modules/#versioning-and-dynamic-inclusion","text":"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.","title":"Versioning and dynamic inclusion"},{"location":"modules/#textual-includes","text":"","title":"Textual includes"},{"location":"modules/#include","text":"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).","title":"$include"},{"location":"modules/#exec","text":"An alternative to $include is $exec which is similar to include, but instead includes the output of an external program as the included text. An example: import std::io; // On Linux or MacOS this will insert 'String a = \"Hello world!\";' $exec(\"echo\", \"String a = \\\\\\\"Hello world!\\\\\\\"\\\\;\"); fn void main() { io::printn(a); } Using $exec requires full trust level , which is enabled with -trust=full from the command line. '$exec' will by default run from the /scripts directory for projects, for non-project builds, the current directory is used as well.","title":"$exec"},{"location":"modules/#exec-scripting","text":"$exec allows a special scripting mode, where one or more C3 files are compiled on the fly and run by $exec . import std::io; // Compile foo.c3 and bar.c3 in the /scripts directory, invoke the resulting binary // with the argument 'test' $exec(\"foo.c3;bar.c3\", \"test\"); fn void main() { ... }","title":"$exec scripting"},{"location":"moreexamples/","text":"More Examples Hello World fn void main() { io::printn(\"Hello world!\"); } Fibonacci recursive fn long fib(long n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); } HTTP Server import net::http_server; fn void! httpHandler(HttpContext* context) { context.response.contentType = \"text/plain\"; context.response.printf(\"Hello world!\\n\"); } fn void main() { HttpServer server; server.init(); InetAddress! addr = server.bindPort(8080); if (catch addr) { printf(\"Failed to open server.\\n\"); exit(-1); } printf(\"Begin listening to on http://%s\\n\", addr.description()); server.listen(&httpHandler); }","title":"More Examples"},{"location":"moreexamples/#more-examples","text":"","title":"More Examples"},{"location":"moreexamples/#hello-world","text":"fn void main() { io::printn(\"Hello world!\"); }","title":"Hello World"},{"location":"moreexamples/#fibonacci-recursive","text":"fn long fib(long n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); }","title":"Fibonacci recursive"},{"location":"moreexamples/#http-server","text":"import net::http_server; fn void! httpHandler(HttpContext* context) { context.response.contentType = \"text/plain\"; context.response.printf(\"Hello world!\\n\"); } fn void main() { HttpServer server; server.init(); InetAddress! addr = server.bindPort(8080); if (catch addr) { printf(\"Failed to open server.\\n\"); exit(-1); } printf(\"Begin listening to on http://%s\\n\", addr.description()); server.listen(&httpHandler); }","title":"HTTP Server"},{"location":"naming/","text":"Naming rules 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); }","title":"Naming rules"},{"location":"naming/#naming-rules","text":"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.","title":"Naming rules"},{"location":"naming/#structs-unions-enums-and-faults","text":"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 }","title":"Structs, unions, enums and faults"},{"location":"naming/#variables-and-parameters","text":"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++; }","title":"Variables and parameters"},{"location":"naming/#global-constants","text":"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;","title":"Global constants"},{"location":"naming/#enum-fault-values","text":"Enum and fault values follow the same naming standard as global constants. enum Baz { VALUE_1, VALUE_2 } fault Err { OOPS, LOTS_OF_OOPS }","title":"Enum / Fault values"},{"location":"naming/#struct-union-members","text":"Struct and union members follow the same naming rules as variables.","title":"Struct / union members"},{"location":"naming/#modules","text":"Module names may contain a-z, 0-9 and _ , no upper case characters are allowed. module foo;","title":"Modules"},{"location":"naming/#functions-and-macros","text":"Functions and macros must start with a-z after any optional initial _ . fn void theMostAmazingFunction() { return; } macro justDoIt(x) { justDo(x); }","title":"Functions and macros"},{"location":"operators/","text":"Operator overloading 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); } }","title":"Operator overloading"},{"location":"operators/#operator-overloading","text":"C3 allows some limited operator overloading for working with containers.","title":"Operator overloading"},{"location":"operators/#element-at-operator","text":"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.","title":"\"Element at\" operator []"},{"location":"operators/#element-ref-operator","text":"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]; }","title":"\"Element ref\" operator &[]"},{"location":"operators/#element-set-operator","text":"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; }","title":"\"Element set\" operator []="},{"location":"operators/#len-operator","text":"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.","title":"\"len\" operator"},{"location":"operators/#enabling-foreach","text":"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); } }","title":"Enabling 'foreach'"},{"location":"optionals/","text":"Optionals and error handling Optionals in C3 work differently from other languages: It is similar to a \"Result\" type in capabilities. It is not quite a type: it can be used for variable and return values - but not for parameters or struct member types. It \"cascades\" the optional-ness of an expression, somewhat reminiscent of the \"flatmap\" operation. A function called with an Optional is only invoked if it has an actual value. What is an Optional? In C3 it is either: A variable that works as a union between a fault value and an actual value, we call this latter the \"result\". A return value that is either a fault or a result. We can construct an Optional by adding the ! suffix to a type: 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: Evaluate the first argument. If it is a fault then we're done, set x to this fault. Evaluate the second argument. If it is a fault then we're done, set x to this fault. 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: 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.","title":"Optionals and error handling"},{"location":"optionals/#optionals-and-error-handling","text":"Optionals in C3 work differently from other languages: It is similar to a \"Result\" type in capabilities. It is not quite a type: it can be used for variable and return values - but not for parameters or struct member types. It \"cascades\" the optional-ness of an expression, somewhat reminiscent of the \"flatmap\" operation. A function called with an Optional is only invoked if it has an actual value.","title":"Optionals and error handling"},{"location":"optionals/#what-is-an-optional","text":"In C3 it is either: A variable that works as a union between a fault value and an actual value, we call this latter the \"result\". A return value that is either a fault or a result. We can construct an Optional by adding the ! suffix to a type: 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);","title":"What is an Optional?"},{"location":"optionals/#optional-execution","text":"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: Evaluate the first argument. If it is a fault then we're done, set x to this fault. Evaluate the second argument. If it is a fault then we're done, set x to this fault. 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))","title":"Optional execution"},{"location":"optionals/#optional-unwrapping","text":"It's not possible to assign an Optional to a non-optional type: int! a = ... int b = a; // <- ERROR, can't assign \"int!\" to \"int\" To assign it we have two options, if-try and implicit unwrap.","title":"Optional unwrapping"},{"location":"optionals/#if-try","text":"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!","title":"If-try"},{"location":"optionals/#if-catch","text":"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. }","title":"If-catch"},{"location":"optionals/#implicit-unwrapping-with-if-catch","text":"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;","title":"Implicit unwrapping with if-catch."},{"location":"optionals/#getting-the-fault-without-unwrapping","text":"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! }","title":"Getting the fault without unwrapping"},{"location":"optionals/#if-catch-switching","text":"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: ... } }","title":"If-catch switching"},{"location":"optionals/#testing-for-a-result-without-unwrapping","text":"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));","title":"Testing for a result without unwrapping"},{"location":"optionals/#fault-and-anyfault","text":"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","title":"fault and anyfault"},{"location":"optionals/#setting-the-result-and-the-fault","text":"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?","title":"Setting the result and the fault"},{"location":"optionals/#rethrow-or-else-and-force-unwrap","text":"Three helper operators are provided for working with Optionals: rethrow ! , or-else ?? and force unwrap !! .","title":"Rethrow, or-else and force unwrap"},{"location":"optionals/#rethrow","text":"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","title":"Rethrow"},{"location":"optionals/#or-else","text":"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;","title":"Or-else"},{"location":"optionals/#force-unwrap","text":"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()!!;","title":"Force unwrap"},{"location":"optionals/#no-void-variables","text":"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());","title":"No void! variables"},{"location":"optionals/#examples","text":"","title":"Examples"},{"location":"optionals/#basic-usage-with-immediate-error-handling","text":"// 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* }","title":"Basic usage with immediate error handling"},{"location":"optionals/#composability-of-calls","text":"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! } )","title":"Composability of calls"},{"location":"optionals/#returning-a-fault","text":"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?; /* ... */ }","title":"Returning a fault"},{"location":"optionals/#calling-a-function-automatically-returning-any-optional-result","text":"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?; }","title":"Calling a function automatically returning any optional result"},{"location":"optionals/#force-unwrapping-to-panic-on-fault","text":"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\"); }","title":"Force unwrapping to panic on fault"},{"location":"optionals/#catching-faults-to-implicitly-unwrap","text":"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*. }","title":"Catching faults to implicitly unwrap"},{"location":"optionals/#only-run-if-there-is-no-fault","text":"fn void do_something_to_file() { void! res = find_file(); if (try res) { io::printn(\"I found the file\"); } }","title":"Only run if there is no fault"},{"location":"optionals/#catching-and-switch-on-fault","text":"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?; } }","title":"Catching and switch on fault"},{"location":"optionals/#default-values-using-or-else","text":"fn int get_int() { return get_int_number_or_fail() ?? -1; }","title":"Default values using or-else"},{"location":"optionals/#get-the-fault-from-an-optional-without-if-catch","text":"fn void test_catch() { int! i = get_something(); anyfault maybe_fault = @catch(i); if (maybe_fault) { // Do something with the fault } }","title":"Get the fault from an optional without if-catch"},{"location":"optionals/#test-if-something-has-a-value-without-if-try","text":"fn void test_something() { int! i = try_it(); bool worked = @ok(i); if (worked) { io::printn(\"Horray! It worked.\"); } }","title":"Test if something has a value without if-try"},{"location":"optionals/#some-common-techniques","text":"Here follows some common techniques using optional values.","title":"Some common techniques"},{"location":"optionals/#catch-and-return-another-error","text":"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 .. }","title":"Catch and return another error"},{"location":"optionals/#using-void-as-a-boolean","text":"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; } ... }","title":"Using void! as a boolean"},{"location":"optionals/#interfacing-with-c","text":"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.","title":"Interfacing with C"},{"location":"precedence/","text":"Precedence rules Precedence rules in C3 differs from C/C++. Here are all precedence levels in C3, listed from highest (1) to lowest (11): () , [] , . , !! postfix ! , ++ and -- @ , prefix - , ~ , prefix * , & , prefix ++ and -- infix * , / , % , *% << , >> ^ , | , infix & + , infix - == , != , >= , <= , > , < && || ternary ?: ?? = , *= , /= , %= , += , -= , <<= , >>= , &= , ^= , |= The main difference is that bitwise operations and shift has higher precedence than addition/subtraction and multiplication/division in C3. Bitwise operations also have higher precedence than the relational operators. Also, there is no difference in precedence between && || or between the bitwise operators. Examples a + b >> c + d (a + b) >> (c + d) // C (+ - are evaluated before >>) a + (b >> c) + d // C3 (>> is evaluated before + -) a & b == c a & (b == c) // C (bitwise operators are evaluated after relational) (a & b) == c // C3 (bitwise operators are evaluated before relational) a > b == c < d (a > b) == (c < d) // C (< > binds tighter than ==) ((a > b) == c) < d // C3 Error, requires parenthesis! a | b ^ c & d a | ((b ^ c) & d) // C (All bitwise operators have different precedence) ((a | b) ^ c) & d // C3 Error, requires parenthesis! The change in precedence of the bitwise operators corrects a long standing issue in the C specification. The change in precedence for shift operations goes towards making the precedence less surprising. Conflating the precedence of relational and equality operations, and all bitwise operations was motivated by simplification: few remember the exact internal differences in precedence between bitwise operators. Parenthesis are required for those conflated levels of precedence. Left-to-right offers a very simple model to think about the internal order of operations, and encourages use of explicit ordering, as best practice in C is to use parentheses anyway.","title":"Precedence"},{"location":"precedence/#precedence-rules","text":"Precedence rules in C3 differs from C/C++. Here are all precedence levels in C3, listed from highest (1) to lowest (11): () , [] , . , !! postfix ! , ++ and -- @ , prefix - , ~ , prefix * , & , prefix ++ and -- infix * , / , % , *% << , >> ^ , | , infix & + , infix - == , != , >= , <= , > , < && || ternary ?: ?? = , *= , /= , %= , += , -= , <<= , >>= , &= , ^= , |= The main difference is that bitwise operations and shift has higher precedence than addition/subtraction and multiplication/division in C3. Bitwise operations also have higher precedence than the relational operators. Also, there is no difference in precedence between && || or between the bitwise operators. Examples a + b >> c + d (a + b) >> (c + d) // C (+ - are evaluated before >>) a + (b >> c) + d // C3 (>> is evaluated before + -) a & b == c a & (b == c) // C (bitwise operators are evaluated after relational) (a & b) == c // C3 (bitwise operators are evaluated before relational) a > b == c < d (a > b) == (c < d) // C (< > binds tighter than ==) ((a > b) == c) < d // C3 Error, requires parenthesis! a | b ^ c & d a | ((b ^ c) & d) // C (All bitwise operators have different precedence) ((a | b) ^ c) & d // C3 Error, requires parenthesis! The change in precedence of the bitwise operators corrects a long standing issue in the C specification. The change in precedence for shift operations goes towards making the precedence less surprising. Conflating the precedence of relational and equality operations, and all bitwise operations was motivated by simplification: few remember the exact internal differences in precedence between bitwise operators. Parenthesis are required for those conflated levels of precedence. Left-to-right offers a very simple model to think about the internal order of operations, and encourages use of explicit ordering, as best practice in C is to use parentheses anyway.","title":"Precedence rules"},{"location":"primer/","text":"A quick primer on C3 for C programmers This primer is intended as a guide to how the C syntax \u2013 and in some cases C semantics \u2013 is different in C3. It is intended to help you take a piece of C code and understand how it can be converted manually to C3. Struct, enum and union declarations Don't add a ; after enum, struct and union declarations, and note the slightly different syntax for declaring a named struct inside of a struct. // C typedef struct { int a; struct { double x; } bar; } Foo; // C3 struct Foo { int a; struct bar { double x; } } Also, user defined types are used without a struct , union or enum keyword, as if the name was a C typedef. Arrays Array sizes are written next to the type and arrays do not decay to pointers, you need to do it manually: // C int x[2] = { 1, 2 }; int *y = x; // C3 int[2] x = { 1, 2 }; int* y = &x; You will probably prefer slices to pointers when passing data around: // C int x[100] = ...; int y[30] = ...; int z[15] = ...; sortMyArray(x, 100); sortMyArray(y, 30); // Sort part of the array! sortMyArray(z + 1, 10); // C3 int[100] x = ...; int[30] y = ...; sortMyArray(&x); // Implicit conversion from int[100]* -> int[] sortMyArray(&y); // Implicit conversion from int[30]* -> int[] sortMyArray(z[1..10]; // Inclusive ranges! Note that declaring an array of inferred size will look different in C3: // C int x[] = { 1, 2, 3 }; // x is int[3] // C3 int[*] x = { 1, 2, 3 }; // x is int[3] Arrays are trivially copyable: // C int x[3] = ...; int y[3]; for (int i = 0; i < 3; i++) y[i] = x[i]; // C3 int[3] x = ...; int[3] y = x; See more here . Undefined behaviour C3 has less undefined behaviour, in particular integers are defined as using 2s complement and signed overflow is wrapping. See more here . Functions Functions are declared like C, but you need to put fn in front: // C: int foo(Foo *b, int x, void *z) { ... } // C3 fn int foo(Foo* b, int x, void* z) { ... } See more about functions, like named and default arguments here . Calling C functions Declare a function (or variable) with extern and it will be possible to access it from C3: // To access puts: extern fn int puts(char*); ... puts(\"Hello world\"); Note that currently only the C standard library is automatically passed to the linker. In order to link with other libraries, you need to explicitly tell the compiler to link them. If you want to use a different identifier inside of your C3 code compared to the function or variable's external name, use the @extern attribute: extern fn int _puts(char* message) @extern(\"puts\"); ... _puts(\"Hello world\"); // <- calls the puts function in libc Identifiers Name standards are enforced: // Starting with uppercase and followed somewhere by at least // one lower case is a user defined type: Foo x; M____y y; // Starting with lowercase is a variable or a function or a member name: x.myval = 1; int z = 123; fn void fooBar(int x) { ... } // Only upper case is a constant or an enum value: const int FOOBAR = 123; enum Test { STATE_A = 0, STATE_B = 2 } Variable declaration Declaring more than one variable at a time is not allowed: // C int a, b; // Not allowed in C3 // C3 int a; int b; In C3, variables are always zero initialized, unless you explicitly opt out using @noinit : // C int a = 0; int b; // C3 int a; int b @noinit; Compound literals Compound literals use C++ style brace initialization, not cast style like in C. For convenience, assigning to a struct will infer the type even if it's not an initializer. // C Foo f = { 1, 2 }; f = (Foo) { 1, 2 }; callFoo((Foo) { 2, 3 }); // C3 Foo f = { 1, 2 }; f = { 1, 2 }; callFoo(Foo{ 2, 3 }); typedef and #define becomes 'def' typedef is replaced by def : // C typedef Foo* FooPtr; // C3 def FooPtr = Foo*; def also allows you to do things that C uses #define for: // C #define println puts #define my_excellent_string my_string char *my_string = \"Party on\"; ... println(my_excellent_string); // C3 def println = puts; def my_excellent_string = my_string; char* my_string = \"Party on\"; ... println(my_excellent_string); Read more about def here . Basic types Several C types that would be variable sized are fixed size, and others changed names: // C int16_t a; int32_t b; int64_t c; uint64_t d; size_t e; ssize_t f; ptrdiff_t g; intptr_t h; // C3 short a; // Guaranteed 16 bits int b; // Guaranteed 32 bits long c; // Guaranteed 64 bits ulong d; // Guaranteed 64 bits int128 e; // Guaranteed 128 bits uint128 f; // Guaranteed 128 bits usz g; // Same as C size_t, depends on target isz h; // Same as C ptrdiff_t iptr i; // Same as intptr_t depends on target uptr j; // Same as uintptr_t depends on target Read more about types here . Instead of #include: Modules and import Declaring the module name is not mandatory, but if you leave it out the file name will be used as the module name. Imports are recursive. module otherlib::foo; fn void test() { ... } struct FooStruct { ... } module mylib::bar; import otherlib; fn void myCheck() { foo::test(); // foo prefix is mandatory. mylib::foo::test(); // This also works; FooStruct x; // But user defined types don't need the prefix. otherlib::foo::FooStruct y; // But it is allowed. } Comments The /* */ comments are nesting /* This /* will all */ be commented out */ Note that doc comments, starting with /** has special rules for parsing it, and is not considered a regular comment. See contracts for more information. Type qualifiers Qualifiers like const and volatile are removed, but const before a constant will make it treated as a compile time constant. The constant does not need to be typed. const A = false; // Compile time $if A: // This will not be compiled $else // This will be compiled $endif volatile is replaced by macros for volatile load and store. Goto removed goto is removed, but there is labelled break and continue as well as defer to handle the cases when it is commonly used in C. // C Foo *foo = malloc(sizeof(Foo)); if (tryFoo(foo)) goto FAIL; if (modifyFoo(foo)) goto FAIL; free(foo); return true; FAIL: free(foo); return false; // C3, direct translation: do FAIL: { Foo *foo = malloc(sizeof(Foo)); if (tryFoo(foo)) break FAIL; if (modifyFoo(foo)) break FAIL; free(foo); return true; } free(foo); return false; // C3, using defer: Foo *foo = malloc(Foo); defer free(foo); if (tryFoo(foo)) return false; if (modifyFoo(foo)) return false; return true; Changes in switch case statements automatically break. Use nextcase to fallthrough to the next statement, but empty case statements have implicit fallthrough: // C switch (a) { case 1: case 2: doOne(); break; case 3: i = 0; case 4: doFour(); break; case 5: doFive(); default: return false; } // C3 switch (a) { case 1: case 2: doOne(); case 3: i = 0; nextcase; case 4: doFour(); case 5: doFive(); nextcase; default: return false; } Note that we can jump to an arbitrary case using C3: // C switch (a) { case 1: doOne(); goto LABEL3; case 2: doTwo(); break; case 3: LABEL3: doThree(); default: return false; } // C3 switch (a) { case 1: doOne(); nextcase 3; case 2: doTwo(); case 3: doThree(); nextcase; default: return false; } Bitfields are replace by explicit bitstructs A bitstruct has an explicit container type, and each field has an exact bit range. bitstruct Foo : short { int a : 0..2; // Exact bit ranges, bits 0-2 uint b : 3..6; MyEnum c : 7..13; } There exists a simplified form for a bitstruct containing only booleans, it is the same except the ranges are left out: struct Flags : char { bool has_hyperdrive; bool has_tractorbeam; bool has_plasmatorpedoes; } For more information see the section on bitstructs . Other changes The following things are enhancements to C, that does not have a direct counterpart in C. Expression blocks Defer Methods Optionals Semantic macros Generic modules Contracts Compile time evaluation Reflection Operator overloading Macro methods Static initialize and finalize functions Dynamic interfaces For the full list of all new features see the feature list . Finally, the FAQ answers many questions you might have as you start out.","title":"A quick primer on C3"},{"location":"primer/#a-quick-primer-on-c3-for-c-programmers","text":"This primer is intended as a guide to how the C syntax \u2013 and in some cases C semantics \u2013 is different in C3. It is intended to help you take a piece of C code and understand how it can be converted manually to C3.","title":"A quick primer on C3 for C programmers"},{"location":"primer/#struct-enum-and-union-declarations","text":"Don't add a ; after enum, struct and union declarations, and note the slightly different syntax for declaring a named struct inside of a struct. // C typedef struct { int a; struct { double x; } bar; } Foo; // C3 struct Foo { int a; struct bar { double x; } } Also, user defined types are used without a struct , union or enum keyword, as if the name was a C typedef.","title":"Struct, enum and union declarations"},{"location":"primer/#arrays","text":"Array sizes are written next to the type and arrays do not decay to pointers, you need to do it manually: // C int x[2] = { 1, 2 }; int *y = x; // C3 int[2] x = { 1, 2 }; int* y = &x; You will probably prefer slices to pointers when passing data around: // C int x[100] = ...; int y[30] = ...; int z[15] = ...; sortMyArray(x, 100); sortMyArray(y, 30); // Sort part of the array! sortMyArray(z + 1, 10); // C3 int[100] x = ...; int[30] y = ...; sortMyArray(&x); // Implicit conversion from int[100]* -> int[] sortMyArray(&y); // Implicit conversion from int[30]* -> int[] sortMyArray(z[1..10]; // Inclusive ranges! Note that declaring an array of inferred size will look different in C3: // C int x[] = { 1, 2, 3 }; // x is int[3] // C3 int[*] x = { 1, 2, 3 }; // x is int[3] Arrays are trivially copyable: // C int x[3] = ...; int y[3]; for (int i = 0; i < 3; i++) y[i] = x[i]; // C3 int[3] x = ...; int[3] y = x; See more here .","title":"Arrays"},{"location":"primer/#undefined-behaviour","text":"C3 has less undefined behaviour, in particular integers are defined as using 2s complement and signed overflow is wrapping. See more here .","title":"Undefined behaviour"},{"location":"primer/#functions","text":"Functions are declared like C, but you need to put fn in front: // C: int foo(Foo *b, int x, void *z) { ... } // C3 fn int foo(Foo* b, int x, void* z) { ... } See more about functions, like named and default arguments here .","title":"Functions"},{"location":"primer/#calling-c-functions","text":"Declare a function (or variable) with extern and it will be possible to access it from C3: // To access puts: extern fn int puts(char*); ... puts(\"Hello world\"); Note that currently only the C standard library is automatically passed to the linker. In order to link with other libraries, you need to explicitly tell the compiler to link them. If you want to use a different identifier inside of your C3 code compared to the function or variable's external name, use the @extern attribute: extern fn int _puts(char* message) @extern(\"puts\"); ... _puts(\"Hello world\"); // <- calls the puts function in libc","title":"Calling C functions"},{"location":"primer/#identifiers","text":"Name standards are enforced: // Starting with uppercase and followed somewhere by at least // one lower case is a user defined type: Foo x; M____y y; // Starting with lowercase is a variable or a function or a member name: x.myval = 1; int z = 123; fn void fooBar(int x) { ... } // Only upper case is a constant or an enum value: const int FOOBAR = 123; enum Test { STATE_A = 0, STATE_B = 2 }","title":"Identifiers"},{"location":"primer/#variable-declaration","text":"Declaring more than one variable at a time is not allowed: // C int a, b; // Not allowed in C3 // C3 int a; int b; In C3, variables are always zero initialized, unless you explicitly opt out using @noinit : // C int a = 0; int b; // C3 int a; int b @noinit;","title":"Variable declaration"},{"location":"primer/#compound-literals","text":"Compound literals use C++ style brace initialization, not cast style like in C. For convenience, assigning to a struct will infer the type even if it's not an initializer. // C Foo f = { 1, 2 }; f = (Foo) { 1, 2 }; callFoo((Foo) { 2, 3 }); // C3 Foo f = { 1, 2 }; f = { 1, 2 }; callFoo(Foo{ 2, 3 });","title":"Compound literals"},{"location":"primer/#typedef-and-define-becomes-def","text":"typedef is replaced by def : // C typedef Foo* FooPtr; // C3 def FooPtr = Foo*; def also allows you to do things that C uses #define for: // C #define println puts #define my_excellent_string my_string char *my_string = \"Party on\"; ... println(my_excellent_string); // C3 def println = puts; def my_excellent_string = my_string; char* my_string = \"Party on\"; ... println(my_excellent_string); Read more about def here .","title":"typedef and #define becomes 'def'"},{"location":"primer/#basic-types","text":"Several C types that would be variable sized are fixed size, and others changed names: // C int16_t a; int32_t b; int64_t c; uint64_t d; size_t e; ssize_t f; ptrdiff_t g; intptr_t h; // C3 short a; // Guaranteed 16 bits int b; // Guaranteed 32 bits long c; // Guaranteed 64 bits ulong d; // Guaranteed 64 bits int128 e; // Guaranteed 128 bits uint128 f; // Guaranteed 128 bits usz g; // Same as C size_t, depends on target isz h; // Same as C ptrdiff_t iptr i; // Same as intptr_t depends on target uptr j; // Same as uintptr_t depends on target Read more about types here .","title":"Basic types"},{"location":"primer/#instead-of-include-modules-and-import","text":"Declaring the module name is not mandatory, but if you leave it out the file name will be used as the module name. Imports are recursive. module otherlib::foo; fn void test() { ... } struct FooStruct { ... } module mylib::bar; import otherlib; fn void myCheck() { foo::test(); // foo prefix is mandatory. mylib::foo::test(); // This also works; FooStruct x; // But user defined types don't need the prefix. otherlib::foo::FooStruct y; // But it is allowed. }","title":"Instead of #include: Modules and import"},{"location":"primer/#comments","text":"The /* */ comments are nesting /* This /* will all */ be commented out */ Note that doc comments, starting with /** has special rules for parsing it, and is not considered a regular comment. See contracts for more information.","title":"Comments"},{"location":"primer/#type-qualifiers","text":"Qualifiers like const and volatile are removed, but const before a constant will make it treated as a compile time constant. The constant does not need to be typed. const A = false; // Compile time $if A: // This will not be compiled $else // This will be compiled $endif volatile is replaced by macros for volatile load and store.","title":"Type qualifiers"},{"location":"primer/#goto-removed","text":"goto is removed, but there is labelled break and continue as well as defer to handle the cases when it is commonly used in C. // C Foo *foo = malloc(sizeof(Foo)); if (tryFoo(foo)) goto FAIL; if (modifyFoo(foo)) goto FAIL; free(foo); return true; FAIL: free(foo); return false; // C3, direct translation: do FAIL: { Foo *foo = malloc(sizeof(Foo)); if (tryFoo(foo)) break FAIL; if (modifyFoo(foo)) break FAIL; free(foo); return true; } free(foo); return false; // C3, using defer: Foo *foo = malloc(Foo); defer free(foo); if (tryFoo(foo)) return false; if (modifyFoo(foo)) return false; return true;","title":"Goto removed"},{"location":"primer/#changes-in-switch","text":"case statements automatically break. Use nextcase to fallthrough to the next statement, but empty case statements have implicit fallthrough: // C switch (a) { case 1: case 2: doOne(); break; case 3: i = 0; case 4: doFour(); break; case 5: doFive(); default: return false; } // C3 switch (a) { case 1: case 2: doOne(); case 3: i = 0; nextcase; case 4: doFour(); case 5: doFive(); nextcase; default: return false; } Note that we can jump to an arbitrary case using C3: // C switch (a) { case 1: doOne(); goto LABEL3; case 2: doTwo(); break; case 3: LABEL3: doThree(); default: return false; } // C3 switch (a) { case 1: doOne(); nextcase 3; case 2: doTwo(); case 3: doThree(); nextcase; default: return false; }","title":"Changes in switch"},{"location":"primer/#bitfields-are-replace-by-explicit-bitstructs","text":"A bitstruct has an explicit container type, and each field has an exact bit range. bitstruct Foo : short { int a : 0..2; // Exact bit ranges, bits 0-2 uint b : 3..6; MyEnum c : 7..13; } There exists a simplified form for a bitstruct containing only booleans, it is the same except the ranges are left out: struct Flags : char { bool has_hyperdrive; bool has_tractorbeam; bool has_plasmatorpedoes; } For more information see the section on bitstructs .","title":"Bitfields are replace by explicit bitstructs"},{"location":"primer/#other-changes","text":"The following things are enhancements to C, that does not have a direct counterpart in C. Expression blocks Defer Methods Optionals Semantic macros Generic modules Contracts Compile time evaluation Reflection Operator overloading Macro methods Static initialize and finalize functions Dynamic interfaces For the full list of all new features see the feature list . Finally, the FAQ answers many questions you might have as you start out.","title":"Other changes"},{"location":"project/","text":"Customizing A Project A new project is provided with a barebone structure in project.json : { \"version\": \"0.1.0\", \"authors\": [ \"John Doe \" ], \"langrev\": \"1\", \"warnings\": [ \"no-unused\" ], // sources compiled \"sources\": [ \"./**\" ], // directories where C3 library files may be found \"dependency-search-paths\": [ \"lib\" ], // libraries to use \"dependencies\": [], // c compiler \"cc\": \"cc\", // c sources \"c-sources\": [ \"./c-source/**\" ], \"targets\": { \"hello_world\": { \"type\": \"executable\" } } } By default, an executable in assumed, but changing the type to \"static-lib\" or \"dynamic-lib\" creates static library and dynamic library targets respectively. This part will be updated, stay tuned Compilation options The project file contains common settings at the top level, that can be overridden by each target, by simply assigning that particular key. So if the top level defines target to be macos-x64 and the actual target defines it to be windows-x64 , then the windows-x64 will be used for compilation. Similarly, compiler command line parameters can be used in turn to override the target setting. targets The list of targets that can be built. dependencies List of C3 libraries (\".c3l\") to use when compiling the target. sources List of source files to compile. cc C compiler to use for compiling C sources (if C sources are compiled together with C3 files). c-sources List of C sources to compile. version Not handled yet Version for library, will also be provided as a compile time constant. authors Not handled yet List of authors to add for library compilation. langrev Not handled yet The language revision to use. config Not added yet Under the config you define external constants (\"key: value\") that will be included in compilation as if they were global macro constants. export Not added yet Define the list of modules to be exported by a library. Not valid for executables. warnings Not completely supported yet List of warnings to enable during compilation. Target options type This mandatory option should be one of \"executable\", \"dynamic-lib\" and \"static-lib\". More types will be added Using environment variables Not supported yet In addition to constants any values starting with \"$\" will be assumed to be environment variables. For example \"$HOME\" would on unix systems return the home directory. For strings that start with $ but should not be interpreted as an environment variable. For example, the string \"\\$HOME\" would be interpreted as the plain string \"$HOME\"","title":"Project Structure"},{"location":"project/#customizing-a-project","text":"A new project is provided with a barebone structure in project.json : { \"version\": \"0.1.0\", \"authors\": [ \"John Doe \" ], \"langrev\": \"1\", \"warnings\": [ \"no-unused\" ], // sources compiled \"sources\": [ \"./**\" ], // directories where C3 library files may be found \"dependency-search-paths\": [ \"lib\" ], // libraries to use \"dependencies\": [], // c compiler \"cc\": \"cc\", // c sources \"c-sources\": [ \"./c-source/**\" ], \"targets\": { \"hello_world\": { \"type\": \"executable\" } } } By default, an executable in assumed, but changing the type to \"static-lib\" or \"dynamic-lib\" creates static library and dynamic library targets respectively. This part will be updated, stay tuned","title":"Customizing A Project"},{"location":"project/#compilation-options","text":"The project file contains common settings at the top level, that can be overridden by each target, by simply assigning that particular key. So if the top level defines target to be macos-x64 and the actual target defines it to be windows-x64 , then the windows-x64 will be used for compilation. Similarly, compiler command line parameters can be used in turn to override the target setting.","title":"Compilation options"},{"location":"project/#targets","text":"The list of targets that can be built.","title":"targets"},{"location":"project/#dependencies","text":"List of C3 libraries (\".c3l\") to use when compiling the target.","title":"dependencies"},{"location":"project/#sources","text":"List of source files to compile.","title":"sources"},{"location":"project/#cc","text":"C compiler to use for compiling C sources (if C sources are compiled together with C3 files).","title":"cc"},{"location":"project/#c-sources","text":"List of C sources to compile.","title":"c-sources"},{"location":"project/#version","text":"Not handled yet Version for library, will also be provided as a compile time constant.","title":"version"},{"location":"project/#authors","text":"Not handled yet List of authors to add for library compilation.","title":"authors"},{"location":"project/#langrev","text":"Not handled yet The language revision to use.","title":"langrev"},{"location":"project/#config","text":"Not added yet Under the config you define external constants (\"key: value\") that will be included in compilation as if they were global macro constants.","title":"config"},{"location":"project/#export","text":"Not added yet Define the list of modules to be exported by a library. Not valid for executables.","title":"export"},{"location":"project/#warnings","text":"Not completely supported yet List of warnings to enable during compilation.","title":"warnings"},{"location":"project/#target-options","text":"","title":"Target options"},{"location":"project/#type","text":"This mandatory option should be one of \"executable\", \"dynamic-lib\" and \"static-lib\". More types will be added","title":"type"},{"location":"project/#using-environment-variables","text":"Not supported yet In addition to constants any values starting with \"$\" will be assumed to be environment variables. For example \"$HOME\" would on unix systems return the home directory. For strings that start with $ but should not be interpreted as an environment variable. For example, the string \"\\$HOME\" would be interpreted as the plain string \"$HOME\"","title":"Using environment variables"},{"location":"reflection/","text":"Reflection 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;","title":"Reflection"},{"location":"reflection/#reflection","text":"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.","title":"Reflection"},{"location":"reflection/#compile-time-reflection","text":"During compile time there are a number of compile time fields that may be accessed directly.","title":"Compile time reflection"},{"location":"reflection/#type-properties","text":"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","title":"Type properties"},{"location":"reflection/#alignof","text":"Returns the alignment in bytes needed for the type. struct Foo @align(8) { int a; } uint a = Foo.alignof; // 8","title":"alignof"},{"location":"reflection/#associated","text":"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\"","title":"associated"},{"location":"reflection/#elements","text":"Returns the element count of an enum or fault. enum FooEnum { BAR, BAZ } int x = FooEnum.elements; // 2","title":"elements"},{"location":"reflection/#inf","text":"Only available for floating point types Returns a representation of floating point \"infinity\".","title":"inf"},{"location":"reflection/#inner","text":"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.","title":"inner"},{"location":"reflection/#kindof","text":"Returns the underlying TypeKind as defined in std::core::types. TypeKind kind = int.kindof; // TypeKind.SIGNED_INT","title":"kindof"},{"location":"reflection/#len","text":"Returns the length of the array. usz len = int[4].len; // 4","title":"len"},{"location":"reflection/#max","text":"Returns the maximum value of the type (only valid for integer and float types). ushort max_ushort = ushort.max; // 65535","title":"max"},{"location":"reflection/#membersof","text":"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 .","title":"membersof"},{"location":"reflection/#min","text":"Returns the minimum value of the type (only valid for integer and float types). ichar min_ichar = ichar.min; // -128","title":"min"},{"location":"reflection/#names","text":"Returns a subarray containing the names of an enum or fault. enum FooEnum { BAR, BAZ } String[] x = FooEnum.names; // [\"BAR\", \"BAZ\"]","title":"names"},{"location":"reflection/#params","text":"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\"","title":"params"},{"location":"reflection/#parentof","text":"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\"","title":"parentof"},{"location":"reflection/#returns","text":"Only available for function types. Returns the typeid of the return type. def TestFunc = fn int(int, double); String s = TestFunc.returns.nameof; // \"int\"","title":"returns"},{"location":"reflection/#sizeof","text":"Returns the size in bytes for the given type, like C sizeof . usz x = Foo.sizeof;","title":"sizeof"},{"location":"reflection/#typeid","text":"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;","title":"typeid"},{"location":"reflection/#values","text":"Returns a subarray containing the values of an enum or fault. enum FooEnum { BAR, BAZ } String x = FooEnum.values[1].nameof; // \"BAR\"","title":"values"},{"location":"reflection/#compile-time-functions","text":"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","title":"Compile time functions"},{"location":"reflection/#alignof_1","text":"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","title":"$alignof"},{"location":"reflection/#defined","text":"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","title":"$defined"},{"location":"reflection/#eval","text":"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 }","title":"$eval"},{"location":"reflection/#evaltype","text":"Similar to $eval but for types: $evaltype(\"float\") f = 12.0f;","title":"$evaltype"},{"location":"reflection/#extnameof","text":"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\"","title":"$extnameof"},{"location":"reflection/#nameof","text":"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\"","title":"$nameof"},{"location":"reflection/#offsetof","text":"Returns the offset of a member in a struct. Foo z; $offsetof(z.y); // => returns 8 on 64 bit, 4 on 32 bit","title":"$offsetof"},{"location":"reflection/#qnameof","text":"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\"","title":"$qnameof"},{"location":"reflection/#sizeof_1","text":"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;","title":"$sizeof"},{"location":"reflection/#stringify","text":"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\"","title":"$stringify"},{"location":"reflection/#typeof","text":"Returns the type of an expression or variable as a type itself. Foo f; $typeof(f) x = f;","title":"$typeof"},{"location":"rejectedideas/","text":"Rejected ideas These are ideas that will not be implemented in C3 with rationale given. Constructors and destructors A fundamental concept in C3 is that data is not \"active\". This is to say there is no code associated with the data implicitly unlike constructors and destructors in an object oriented language. Not having constructors / destructors prevents RAII-style resource handling, but also allows the code to assume the memory can be freely allocated and initialized as it sees fit, without causing any corruption or undefined behaviour. There is a fundamental difference between active objects and inert data, each has its advantages and disadvantages. C3 follows the C model, which is that data is passive and does not enforce any behaviour. This has very deep implications on the semantics of the language and adding constructors and destructors would change the language greatly, requiring modification of many parts of the language altering. For that reason constructors and destructors will not be considered for C3. Unicode identifiers The main argument for unicode identifiers is that \"it allows people to code in their own language\". However, there is no proof that this actually is used in practice. Furthermore there are practical issues, such as bidirectional text, characters with different code points that are rendered in an identical way etc. Given the complexity and the lack of actual proven benefit, unicode identifiers will not happen for C3.","title":"Rejected ideas"},{"location":"rejectedideas/#rejected-ideas","text":"These are ideas that will not be implemented in C3 with rationale given.","title":"Rejected ideas"},{"location":"rejectedideas/#constructors-and-destructors","text":"A fundamental concept in C3 is that data is not \"active\". This is to say there is no code associated with the data implicitly unlike constructors and destructors in an object oriented language. Not having constructors / destructors prevents RAII-style resource handling, but also allows the code to assume the memory can be freely allocated and initialized as it sees fit, without causing any corruption or undefined behaviour. There is a fundamental difference between active objects and inert data, each has its advantages and disadvantages. C3 follows the C model, which is that data is passive and does not enforce any behaviour. This has very deep implications on the semantics of the language and adding constructors and destructors would change the language greatly, requiring modification of many parts of the language altering. For that reason constructors and destructors will not be considered for C3.","title":"Constructors and destructors"},{"location":"rejectedideas/#unicode-identifiers","text":"The main argument for unicode identifiers is that \"it allows people to code in their own language\". However, there is no proof that this actually is used in practice. Furthermore there are practical issues, such as bidirectional text, characters with different code points that are rendered in an identical way etc. Given the complexity and the lack of actual proven benefit, unicode identifiers will not happen for C3.","title":"Unicode identifiers"},{"location":"sample/","text":"Sample 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; }","title":"More code examples"},{"location":"sample/#sample","text":"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; }","title":"Sample"},{"location":"setup/","text":"Setup C3 has precompiled binaries for Windows, MacOS and Ubuntu. For other platforms it should be possible to compile it on any platform LLVM can compile to. You will need CMake installed. 1. Install LLVM See LLVM the LLVM documentation on how to set up LLVM for development. On MacOS, installing through Homebrew works fine. Using apt-get on Linux should work fine as well. For Windows you can download suitable pre-compiled LLVM binaries from https://github.com/c3lang/win-llvm 2. Clone the C3 compiler source code from Github This should be as simple as doing: git clone https://github.com/c3lang/c3c.git ... from the command line. 3. Build the compiler Create the build directory: MyMachine:c3c$ mkdir build MyMachine:c3c$ cd build/ Use CMake to set up: MyMachine:c3c/build$ cmake ../ Build the compiler: MyMachine:c3c/build$ make 4. Test it out MyMachine:c3c/build$ ./c3c compile-run ../resources/testfragments/helloworld.c3","title":"Setup"},{"location":"setup/#setup","text":"C3 has precompiled binaries for Windows, MacOS and Ubuntu. For other platforms it should be possible to compile it on any platform LLVM can compile to. You will need CMake installed.","title":"Setup"},{"location":"setup/#1-install-llvm","text":"See LLVM the LLVM documentation on how to set up LLVM for development. On MacOS, installing through Homebrew works fine. Using apt-get on Linux should work fine as well. For Windows you can download suitable pre-compiled LLVM binaries from https://github.com/c3lang/win-llvm","title":"1. Install LLVM"},{"location":"setup/#2-clone-the-c3-compiler-source-code-from-github","text":"This should be as simple as doing: git clone https://github.com/c3lang/c3c.git ... from the command line.","title":"2. Clone the C3 compiler source code from Github"},{"location":"setup/#3-build-the-compiler","text":"Create the build directory: MyMachine:c3c$ mkdir build MyMachine:c3c$ cd build/ Use CMake to set up: MyMachine:c3c/build$ cmake ../ Build the compiler: MyMachine:c3c/build$ make","title":"3. Build the compiler"},{"location":"setup/#4-test-it-out","text":"MyMachine:c3c/build$ ./c3c compile-run ../resources/testfragments/helloworld.c3","title":"4. Test it out"},{"location":"specification/","text":"Specification 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: White space Comment Doc Comment Token A token may be: Identifier Keyword Literal Separator Operator A Doc Comment consists of: A stream of descriptive text A list of directive Tokens Those input elements that are not white space or comments are tokens. The tokens are the terminal symbols of the syntactic grammar. Whitespace and comments can serve to separate tokens that might be tokenized in another manner. For example the characters + and = may form the operator token += only if there is no intervening white space or comment. White Space White space is defined as the ASCII horizontal tab character (U+0009), form feed character (U+000A), vertical tab ( U+000B), carriage return (U+000D), space character (U+0020) and the line terminator character (U+000D). WHITESPACE ::= [ \\t\\f\\v\\r\\n] Letters and digits UC_LETTER ::= [A-Z] LC_LETTER ::= [a-z] LETTER ::= UC_LETTER | LC_LETTER DIGIT ::= [0-9] HEX_DIGIT ::= [0-9a-fA-F] BINARY_DIGIT ::= [01] OCTAL_DIGIT ::= [0-7] LC_LETTER_US ::= LC_LETTER | \"_\" UC_LETTER_US ::= UC_LETTER | \"_\" ALPHANUM ::= LETTER | DIGIT ALPHANUM_US ::= ALPHANUM | \"_\" UC_ALPHANUM_US ::= UC_LETTER_US | DIGIT LC_ALPHANUM_US ::= LC_LETTER_US | DIGIT Comments There are three types of regular comments: // text a line comment. The text between // and line end is ignored. /* text */ block comments. The text between /* and */ is ignored. It has nesting behaviour, so for every /* discovered between the first /* and the last */ a corresponding */ must be found. Doc comments /** 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: \\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 , \\u00FF , \\U000000FF and \\xc3\\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF . STRING_LIT ::= \\x22 (CHAR_LIT_BYTE | UNICODE_CHAR)* \\x22 Compile time string concatenation Strings will concatenate if declared in sequence. Example: String s = \"abc\" \"def\" \"ghi\"; // This is equivalent to: String s = \"abcdefghi\"; Raw string literals Raw string literals are enclosed between `` and consist of the raw UTF8 in the source code between the \"`\". A sequence of two \"`\" will be interpreted as a single escaped \"`\" that does not terminate the literal. Compile time concatenation Raw strings will concatenate with other regular strings and raw strings ( see string literal compile time concatenation ). Source code pre-filtering The source code will pre-filter \\r ( 0x0D ) from the source code. This means that it is also implicitly filtered out of raw strings. Character literals A character literal is enclosed in ' and may either consist of 1, 2, 4, 8, 16 bytes. CHARACTER_LIT ::= \"'\" (CHAR_LIT_BYTE+) | UNICODE_CHAR \"'\" Types Types consist of built-in types and user-defined types (enums, structs, unions, bitstructs, fault and distinct). Boolean types bool may have the two values true and false . It holds a single bit of information but is stored in a char type. Integer types The built-in integer types: char unsigned 8-bit ichar signed 8-bit ushort unsigned 16-bit short signed 16-bit uint unsigned 32-bit int signed 32-bit ulong unsigned 64-bit long signed 64-bit uint128 unsigned 128-bit int128 singed 128-bit In addition, the following type aliases exist: uptr unsigned pointer size iptr signed pointer size usz unsigned pointer offset / object size isz signed pointer offset / object size Floating point types Built-in floating point types: float16 IEEE 16-bit* bfloat16 Brainfloat* float IEEE 32-bit double IEEE 64-bit float128 IEEE 128-bit* (* optionally supported) Vector types A vector lowers to the platform's vector types where available. A vector has a base type and a width. vector_type ::= type \"[<\" length \">]\" Vector base type The base type of a vector must be boolean, an integer or a floating point type. Min width The vector width must be at least 1. Element access Vector elements are accessed using [] . It is possible to take the address of a single element. Alignment Alignment of vectors are platform dependent, but is at least the alignment of its element type. Vector operations Vectors support the same arithmetics as its underlying type, and will perform the operation element-wise. Example: int[<2>] a = { 1, 3 }; int[<2>] b = { 2, 7 }; int[<2>] c = a * b; // Equivalent to int[<2>] c = { a[0] * b[0], a[1] * b[1] }; Array types An array has the alignment of its elements. An array must have at least one element. 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. pointer_type ::= type \"*\" Pointee type The type of the memory pointed to is the pointee type . It may be any runtime type. iptr and uptr A pointer may be losslessly cast to an iptr or uptr . An iptr or uptr may be cast to a pointer of any type. The wildcard pointer void* The void* may implicitly cast into any other pointer type. The void* [implicitly casts into any other pointer. A void* pointer may never be dereferenced. Pointer arithmetic on void* Performing pointer arithmetics on void* will assume that the element size is 1. This includes pointer arithmetics using subscripting. Subscripting Subscripting a pointer is equal to performing pointer arithmetics using the index, followed by a deref. Subscripts on pointers may be negative and will never do bounds checks. Deref Dereferencing a pointer will return the value in the memory location interpreted as the pointee type . Struct types A struct may not have zero members. Alignment A non-packed struct has the alignment of the member that has the highest alignment. A packed struct has alignment 1. See align attribute for details on changing the alignment. Union types A union may not have zero members. Alignment A union has the alignment of the member that has the highest alignment. See align attribute for details on changing the alignment. Fault types A fault is an extensible enum which can be used to create an optional . Alignment A fault type has the same alignment as a pointer. See align attribute for details on changing the alignment. Enum types Function types Typeid type The typeid is a pointer sized value which uniquely identifies a type. Any* type The any* is a fat pointer (2 pointers wide) holding a pointer to a value and its corresponding typeid . It cannot be dereferenced. Fields .ptr returns a void* pointer to the underlying value .type returns the typeid of the underlying value. Switching over any Switching over an any value creates an any switch . Anyfault type Declarations and scope Expressions Assignment expression assignment_expr ::= ct_type_assign | unary_expr assignment_op expr ct_type_assign ::= ct_type_ident \"=\" type assignment_op ::= \"=\" | \"+=\" | \"-=\" | \"*=\" | \"/=\" | \"%=\" | \"<<=\" | \">>=\" | \"&=\" | \"^=\" | \"|=\" Type assign This assigns a new type to a compile time type variable. The value of the expression is the type assigned. Combined assign All assignment operations except for \"=\" are combined assign operation. They first perform the operation indicated by the leftmost character(s) in the operator (e.g + for += , << for <<= etc) with the lhs and the rhs. The result is then assigned to the left hand side. The result of the operation is the new value of the left hand side. Implicit conversion If the left hand side is a pointer and the operation is \"+=\" or \"-=\" an attempt to implicitly convert to isz/usz will be tried. For all other types and operations, an implicit conversion of rhs to the type of lhs will be tried. Ternary, elvis and or-else expressions ternary_group_expr ::= suffix_group_expr | ternary_expr | elvis_expr | orelse_expr ternary_expr ::= or_expr \"?\" expr \":\" ternary_group_expr elvis_expr ::= suffix_expr \"?:\" ternary_group_expr orelse_expr ::= suffix_expr \"??\" ternary_group_expr Ternary evaluation The most left-hand expression is evaluated to a boolean. If it is true, the value of the middle expression is returned, otherwise the last expression is returned. Only the most left-hand expression and the returned expressions are evaluated. The middle and last expression are implicitly converted to their unified type . The resulting type is the unified type . Elvis evaluation Lhs and rhs are implicitly converted to their unified type . The lhs is evaluated, it is then converted to a boolean, if the result it true, return the lhs value before its boolean conversion. Otherwise return the right hand side. The right hand side is only evaluated if the lhs evaluates to false. The resulting type is the unified type . Orelse evaluation The lhs must be optional. The non-optional type for lhs and rhs are calculated. The unified type of the result is calculated. Lhs are converted to the unified type preserving their optionality. At runtime, lhs is evaluated. If it evaluates to an optional, rhs is returned instead. Rhs is only evaluated if lhs evaluates to an optional. The resulting type of the orelse is the post conversion type of the rhs. Suffix expression Suffix expressions convert a fault to an optional. suffix_group_exp ::= or_group_expr | suffix_expr suffix_expr ::= or_group_expr \"?\" \"!\"? Effect of \"?\" The \"?\" will convert the expression into an optional. The left hand side must be a fault type. If an optional \"!\" follows, this optional is immediately returned, as if by a return ? 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: void* a = &a; // Valid int a = a + 1; // Invalid Defer statement The defer statements are executed at (runtime) scope exit, whether through return , break , continue or rethrow. defer_stmt ::= \"defer\" (\"try\" | \"catch\")? stmt Defer in defer The defer body (statement) may not be a defer statement. However, if the body is a compound statement then this may have any number of defer statements. Static and tlocal variables in defer Static and tlocal variables are allowed in a defer statement. Only a single variable is instantiated regardless of the number of inlining locations. Defer and return If the return has an expression, then it is evaluated before the defer statements (due to exit from the current function scope), are executed. Example: int a = 0; defer a++; return a; // This is equivalent to int a = 0; int temp = a; a++; return temp; Defer and jump statements A defer body may not contain a break , continue , return or rethrow that would exit the statement. Defer execution Defer statements are executed in the reverse order of their declaration, starting from the last declared defer statement. Defer try A defer try type of defer will only execute if the scope is left through normal fallthrough, break , continue or a return with a result. It will not execute if the exit is through a rethrow or a return with an optional value. Defer catch A defer catch type of defer will only execute if the scope is left through a rethrow or a return with an optional value It will not execute if the exit is a normal fallthrough, break , continue or a return with a result. Non-regular returns - longjmp, panic and other errors Defers will not execute when doing longjmp terminating through a panic or other error. They are only invoked on regular scope exits. Expr statement An expression statement evaluates an expression. expr_stmt ::= expr \";\" No discard If the expression is a function or macro call either returning an optional or annotated @nodiscard , then the expression is a compile time error. A function or macro returning an optional can use the @maydiscard attribute to suppress this error. If statement An if statement will evaluate the cond expression, then execute the first statement (the \"then clause\") in the if-body if it evaluates to \"true\", otherwise execute the else clause. If no else clause exists, then the next statement is executed. if_stmt ::= \"if\" (label \":\")? \"(\" cond_expr \")\" if_body if_body ::= non_compound_stmt | compound_stmt else_clause? | \"{\" switch_body \"}\" else_clause ::= \"else\" (if_stmt | compound_stmt) Scopes Both the \"then\" clause and the else clause open new scopes, even if they are non-compound statements. The cond expression scope is valid until the exit of the entire statement, so any declarations in the cond expression are available both in then and else clauses. Declarations in the \"then\" clause is not available in the else clause and vice versa. Special parsing of the \"then\" clause If the then-clause isn't a compound statement, then it must follow on the same row as the cond expression. It may not appear on a consecutive row. Break It is possible to use labelled break to break out of an if statement. Note that an unlabelled break may not be used. If-try The cond expression may be a try-unwrap chain. In this case, the unwrapped variables are scoped to the \"then\" clause only. If-catch The cond expression may be a catch-unwrap. The unwrap is scoped to the \"then\" clause only. If one or more variables are in the catch, then the \"else\" clause have these variables implicitly unwrapped. Example: int! a = foo(); int! b = foo(); if (catch a, b) { // Do something } else { int x = a + b; // Valid, a and b are implicitly unwrapped. } If-catch implicit unwrap If an if-catch's \"then\"-clause will jump out of the outer scope in all code paths and the catch is on one or more variables, then this variable(s) will be implicitly unwrapped in the outer scope after the if-statement. Example: int! a = foo(); if (catch a) { return; } int x = a; // Valid, a is implicitly unwrapped. Nextcase statement Nextcase will jump to another switch case. nextcase_stmt ::= \"nextcase\" ((label \":\")? (expr | \"default\"))? \";\" Labels When a nextcase has a label, the jump is to the switch in an outer scope with the corresponding label. No expression jumps A nextcase without any expression jumps to the next case clause in the current switch. It is not possible to use no expression nextcase with labels. Jumps to default Using default jumps to the default clause of a switch. Missing case If the switch has constant case values, and the nextcase expression is constant, then the value of the expression must match a case clause. Not matching a case is a compile time error. If one or more cases are non-constant and/or the nextcase expression is non-constant, then no compile time check is made. Variable expression If the nextcase has a non-constant expression, or the cases are not all constant, then first the nextcase expression is evaluated. Next, execution will proceed as if the switch was invoked again, but with the nextcase expression as the switch cond expression. See switch statement . If the switch does not have a cond expression, nextcase with an expression is not allowed. Unreachable code Any statement in the same scope after a nextcase are considered unreachable . Switch statement switch_stmt ::= \"switch\" (label \":\")? (\"(\" cond_expr \")\")? switch body switch_body ::= \"{\" case_clause* \"}\" case_clause ::= default_stmt | case_stmt default_stmt ::= \"default\" \":\" stmt* case_stmt ::= \"case\" label? expr (\"..\" expr)? \":\" stmt* Regular switch If the cond expression exists and all case statements have constant expression, then first the cond expression is evaluated, next the case corresponding to the expression's value will be jumped to and the statement will be executed. After reaching the end of the statements and a new case clause or the end of the switch body, the execution will jump to the first statement after the switch. If-switch If the cond expression is missing or the case statements are non-constant expressions, then each case clause will be evaluated in order after the cond expression has been evaluated (if it exists): If a cond expression exists, calculate the case expression and execute the case if it is matching the cond expression. A default statement has no expression and will always be considered matching the cond expression reached. If no con expression exists, calculate the case expression and execute the case if the expression evaluates to \"true\" when implicitly converted to boolean. A default statement will always be considered having the \"true\" result. Any-switch If the cond expression is an any* type, the switch is handled as if switching was done over the type field of the any* . This field has the type of typeid , and the cases follows the rules for switching over typeid . If the cond expression is a variable, then this variable is implicitly converted to a pointer with the pointee type given by the case statement. Example: any* a = abc(); switch (a) { case int: int b = *a; // a is int* case float: float z = *a; // a is float* case Bar: Bar f = *a; // a is Bar* default: // a is not unwrapped } Ranged cases Cases may be ranged. The start and end of the range must both be constant integer values. The start must be less or equal to the end value. Using non-integers or non-constant values is a compile time error. Fallthrough If a case clause has no statements, then when executing the case, rather than exiting the switch, the next case clause immediately following it will be executed. If that one should also be missing statement, the procedure will be repeated until a case clause with statements is encountered (and executed), or the end of the switch is reached. Exhaustive switch If a switch case has a default clause or it is switching over an enum and there exists a case for each enum value then the switch is exhaustive. Break If an unlabelled break, or a break with the switch's label is encountered, then the execution will jump out of the switch and proceed directly after the end of the switch body. Unreachable code If a switch is exhaustive and all case clauses end with a jump instruction, containing no break statement out of the current switch, then the code directly following the switch will be considered unreachable . Switching over typeid If the switch cond expression is a typeid, then case declarations may use only the type name after the case, which will be interpreted as having an implicit .typeid . Example: case int: will be interpreted as if written case int.typeid . Nextcase without expression Without a value nextcase will jump to the beginning of the next case clause. It is not allowed to put nextcase without an expression if there are no following case clauses. Nextcase with expression Nextcase with an expression will evaluate the expression and then jump as if the switch was entered with the cond expression corresponding to the value of the nextcase expression. Nextcase with an expression cannot be used on a switch without a cond expression. Do statement The do statement first evaluates its body (inner statement), then evaluates the cond expression. If the cond expression evaluates to true, jumps back into the body and repeats the process. do_stmt ::= \"do\" label? compound_stmt (\"while\" \"(\" cond_expr \")\")? \";\" Unreachable code The statement after a do is considered unreachable if the cond expression cannot ever be false and there is no break out of the do. Break break will exit the do with execution continuing on the following statement. Continue continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached. Do block If no while part exists, it will only execute the block once, as if it ended with while (false) , this is called a \"do block\" For statement The for statement will perform the (optional) init expression. The cond expression will then be tested. If it evaluates to true then the body will execute, followed by the incr expression. After execution will jump back to the cond expression and execution will repeat until the cond expression evaluates to false . for_stmt ::= \"for\" label? \"(\" init_expr \";\" cond_expr? \";\" incr_expr \")\" stmt init_expr ::= decl_expr_list? incr_expr ::= expr_list? Init expression The init expression is only executed once before the rest of the for loop is executed. Any declarations in the init expression will be in scope until the for loop exits. The init expression may optionally be omitted. Incr expression The incr expression is evaluated before evaluating the cond expr every time except for the first one. The incr expression may optionally be omitted. Cond expression The cond expression is evaluated every loop. Any declaration in the cond expression is scoped to the current loop, i.e. it will be reinitialized at the start of every loop. The cond expression may optionally be omitted. This is equivalent to setting the cond expression to always return true . Unreachable code The statement after a for is considered unreachable if the cond expression cannot ever be false, or is omitted and there is no break out of the loop. Break break will exit the for with execution continuing on the following statement after the for . Continue continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached. Equivalence of while and for A while loop is functionally equivalent to a for loop without init and incr expressions. Foreach and foreach_r statements The foreach statement will loop over a sequence of values. The foreach_r is equivalent to foreach but the order of traversal is reversed. foreach starts with element 0 and proceeds step by step to element len - 1 . foreach_r starts starts with element len - 1 and proceeds step by step to element 0 . foreach_stmt ::= \"foreach\" label? \"(\" foreach_vars \":\" expr \")\" stmt foreach_r_stmt ::= \"foreach_r\" label? \"(\" foreach_vars \":\" expr \")\" stmt foreach_vars ::= (foreach_index \",\")? foreach_var foreach_var ::= type? \"&\"? IDENTIFIER Break break will exit the foreach statement with execution continuing on the following statement after. Continue continue will cause the next iteration to commence, as if the end of the statement had been reached. Iteration by value or reference Normally iteration are by value. Each element is copied into the foreach variable. If & is added before the variable name, the elements will be retrieved by reference instead, and consequently the type of the variable will be a pointer to the element type instead. Foreach variable The foreach variable may omit the type. In this case the type is inferred. If the type differs from the element type, then an implicit conversion will be attempted. Failing this is a compile time error. Foreach index If a variable name is added before the foreach variable, then this variable will receive the index of the element. For foreach_r this mean that the first value of the index will be len - 1 . The index type defaults to usz . If an optional type is added to the index, the index will be converted to this type. The type must be an integer type. The conversion happens as if the conversion was a direct cast. If the actual index value would exceed the maximum representable value of the type, this does not affect the actual iteration, but may cause the index value to take on an incorrect value due to the cast. For example, if the optional index type is char and the actual index is 256 , then the index value would show 0 as (char)256 evaluates to zero. Modifying the index variable will not affect the foreach iteration. Foreach support Foreach is natively supported for any 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.","title":"Specification"},{"location":"specification/#specification","text":"THIS SPECIFICATION IS UNDER DEVELOPMENT","title":"Specification"},{"location":"specification/#notation","text":"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.","title":"Notation"},{"location":"specification/#source-code-representation","text":"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.","title":"Source code representation"},{"location":"specification/#lexical-translations","text":"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.","title":"Lexical Translations"},{"location":"specification/#line-terminators","text":"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.","title":"Line Terminators"},{"location":"specification/#input-elements-and-tokens","text":"An input element may be: White space Comment Doc Comment Token A token may be: Identifier Keyword Literal Separator Operator A Doc Comment consists of: A stream of descriptive text A list of directive Tokens Those input elements that are not white space or comments are tokens. The tokens are the terminal symbols of the syntactic grammar. Whitespace and comments can serve to separate tokens that might be tokenized in another manner. For example the characters + and = may form the operator token += only if there is no intervening white space or comment.","title":"Input Elements and Tokens"},{"location":"specification/#white-space","text":"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]","title":"White Space"},{"location":"specification/#letters-and-digits","text":"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","title":"Letters and digits"},{"location":"specification/#comments","text":"There are three types of regular comments: // text a line comment. The text between // and line end is ignored. /* text */ block comments. The text between /* and */ is ignored. It has nesting behaviour, so for every /* discovered between the first /* and the last */ a corresponding */ must be found.","title":"Comments"},{"location":"specification/#doc-comments","text":"/** 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.","title":"Doc comments"},{"location":"specification/#identifiers","text":"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*","title":"Identifiers"},{"location":"specification/#keywords","text":"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","title":"Keywords"},{"location":"specification/#operators-and-punctuation","text":"The following character sequences represent operators and punctuation. & @ ~ | ^ : , / $ . ; ) > < # { } - ( ) * [ ] % >= <= + += -= ! ? ?: && ?? &= |= ^= /= .. == ({ }) [< >] (< >) ++ -- %= != || :: << >> !! ... <<= >>=","title":"Operators and punctuation"},{"location":"specification/#integer-literals","text":"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","title":"Integer literals"},{"location":"specification/#floating-point-literals","text":"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","title":"Floating point literals"},{"location":"specification/#characters","text":"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","title":"Characters"},{"location":"specification/#backslash-escapes","text":"The following backslash escapes are available for characters and string literals: \\0 0x00 zero value \\a 0x07 alert/bell \\b 0x08 backspace \\e 0x1B escape \\f 0x0C form feed \\n 0x0A newline \\r 0x0D carriage return \\t 0x09 horizontal tab \\v 0x0B vertical tab \\\\ 0x5C backslash \\' 0x27 single quote ' \\\" 0x22 double quote \" \\x Escapes a single byte hex value \\u Escapes a two byte unicode hex value \\U Escapes a four byte unicode hex value","title":"Backslash escapes"},{"location":"specification/#string-literals","text":"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 , \\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","title":"String literals"},{"location":"specification/#compile-time-string-concatenation","text":"Strings will concatenate if declared in sequence. Example: String s = \"abc\" \"def\" \"ghi\"; // This is equivalent to: String s = \"abcdefghi\";","title":"Compile time string concatenation"},{"location":"specification/#raw-string-literals","text":"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.","title":"Raw string literals"},{"location":"specification/#compile-time-concatenation","text":"Raw strings will concatenate with other regular strings and raw strings ( see string literal compile time concatenation ).","title":"Compile time concatenation"},{"location":"specification/#source-code-pre-filtering","text":"The source code will pre-filter \\r ( 0x0D ) from the source code. This means that it is also implicitly filtered out of raw strings.","title":"Source code pre-filtering"},{"location":"specification/#character-literals","text":"A character literal is enclosed in ' and may either consist of 1, 2, 4, 8, 16 bytes. CHARACTER_LIT ::= \"'\" (CHAR_LIT_BYTE+) | UNICODE_CHAR \"'\"","title":"Character literals"},{"location":"specification/#types","text":"Types consist of built-in types and user-defined types (enums, structs, unions, bitstructs, fault and distinct).","title":"Types"},{"location":"specification/#boolean-types","text":"bool may have the two values true and false . It holds a single bit of information but is stored in a char type.","title":"Boolean types"},{"location":"specification/#integer-types","text":"The built-in integer types: char unsigned 8-bit ichar signed 8-bit ushort unsigned 16-bit short signed 16-bit uint unsigned 32-bit int signed 32-bit ulong unsigned 64-bit long signed 64-bit uint128 unsigned 128-bit int128 singed 128-bit In addition, the following type aliases exist: uptr unsigned pointer size iptr signed pointer size usz unsigned pointer offset / object size isz signed pointer offset / object size","title":"Integer types"},{"location":"specification/#floating-point-types","text":"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)","title":"Floating point types"},{"location":"specification/#vector-types","text":"A vector lowers to the platform's vector types where available. A vector has a base type and a width. vector_type ::= type \"[<\" length \">]\"","title":"Vector types"},{"location":"specification/#vector-base-type","text":"The base type of a vector must be boolean, an integer or a floating point type.","title":"Vector base type"},{"location":"specification/#min-width","text":"The vector width must be at least 1.","title":"Min width"},{"location":"specification/#element-access","text":"Vector elements are accessed using [] . It is possible to take the address of a single element.","title":"Element access"},{"location":"specification/#alignment","text":"Alignment of vectors are platform dependent, but is at least the alignment of its element type.","title":"Alignment"},{"location":"specification/#vector-operations","text":"Vectors support the same arithmetics as its underlying type, and will perform the operation element-wise. Example: int[<2>] a = { 1, 3 }; int[<2>] b = { 2, 7 }; int[<2>] c = a * b; // Equivalent to int[<2>] c = { a[0] * b[0], a[1] * b[1] };","title":"Vector operations"},{"location":"specification/#array-types","text":"An array has the alignment of its elements. An array must have at least one element.","title":"Array types"},{"location":"specification/#subarray-types","text":"The subarray consist of a pointer, followed by an usz length, having the alignment of pointers.","title":"Subarray types"},{"location":"specification/#pointer-types","text":"A pointer is the address to memory. pointer_type ::= type \"*\"","title":"Pointer types"},{"location":"specification/#pointee-type","text":"The type of the memory pointed to is the pointee type . It may be any runtime type.","title":"Pointee type"},{"location":"specification/#iptr-and-uptr","text":"A pointer may be losslessly cast to an iptr or uptr . An iptr or uptr may be cast to a pointer of any type.","title":"iptr and uptr"},{"location":"specification/#the-wildcard-pointer-void","text":"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.","title":"The wildcard pointer void*"},{"location":"specification/#pointer-arithmetic-on-void","text":"Performing pointer arithmetics on void* will assume that the element size is 1. This includes pointer arithmetics using subscripting.","title":"Pointer arithmetic on void*"},{"location":"specification/#subscripting","text":"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.","title":"Subscripting"},{"location":"specification/#deref","text":"Dereferencing a pointer will return the value in the memory location interpreted as the pointee type .","title":"Deref"},{"location":"specification/#_1","text":"","title":""},{"location":"specification/#struct-types","text":"A struct may not have zero members.","title":"Struct types"},{"location":"specification/#alignment_1","text":"A non-packed struct has the alignment of the member that has the highest alignment. A packed struct has alignment 1. See align attribute for details on changing the alignment.","title":"Alignment"},{"location":"specification/#union-types","text":"A union may not have zero members.","title":"Union types"},{"location":"specification/#alignment_2","text":"A union has the alignment of the member that has the highest alignment. See align attribute for details on changing the alignment.","title":"Alignment"},{"location":"specification/#fault-types","text":"A fault is an extensible enum which can be used to create an optional .","title":"Fault types"},{"location":"specification/#alignment_3","text":"A fault type has the same alignment as a pointer. See align attribute for details on changing the alignment.","title":"Alignment"},{"location":"specification/#enum-types","text":"","title":"Enum types"},{"location":"specification/#function-types","text":"","title":"Function types"},{"location":"specification/#typeid-type","text":"The typeid is a pointer sized value which uniquely identifies a type.","title":"Typeid type"},{"location":"specification/#any-type","text":"The any* is a fat pointer (2 pointers wide) holding a pointer to a value and its corresponding typeid . It cannot be dereferenced.","title":"Any* type"},{"location":"specification/#fields","text":".ptr returns a void* pointer to the underlying value .type returns the typeid of the underlying value.","title":"Fields"},{"location":"specification/#switching-over-any","text":"Switching over an any value creates an any switch .","title":"Switching over any"},{"location":"specification/#anyfault-type","text":"","title":"Anyfault type"},{"location":"specification/#declarations-and-scope","text":"","title":"Declarations and scope"},{"location":"specification/#expressions","text":"","title":"Expressions"},{"location":"specification/#assignment-expression","text":"assignment_expr ::= ct_type_assign | unary_expr assignment_op expr ct_type_assign ::= ct_type_ident \"=\" type assignment_op ::= \"=\" | \"+=\" | \"-=\" | \"*=\" | \"/=\" | \"%=\" | \"<<=\" | \">>=\" | \"&=\" | \"^=\" | \"|=\"","title":"Assignment expression"},{"location":"specification/#type-assign","text":"This assigns a new type to a compile time type variable. The value of the expression is the type assigned.","title":"Type assign"},{"location":"specification/#combined-assign","text":"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.","title":"Combined assign"},{"location":"specification/#implicit-conversion","text":"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.","title":"Implicit conversion"},{"location":"specification/#ternary-elvis-and-or-else-expressions","text":"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","title":"Ternary, elvis and or-else expressions"},{"location":"specification/#ternary-evaluation","text":"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 .","title":"Ternary evaluation"},{"location":"specification/#elvis-evaluation","text":"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 .","title":"Elvis evaluation"},{"location":"specification/#orelse-evaluation","text":"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.","title":"Orelse evaluation"},{"location":"specification/#suffix-expression","text":"Suffix expressions convert a fault to an optional. suffix_group_exp ::= or_group_expr | suffix_expr suffix_expr ::= or_group_expr \"?\" \"!\"?","title":"Suffix expression"},{"location":"specification/#effect-of","text":"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.","title":"Effect of \"?\""},{"location":"specification/#type-of-the-expression","text":"The type is a wildcard optional . If \"!\" is added, it is a wildcard type.","title":"Type of the expression"},{"location":"specification/#rethrow-expression","text":"If the expression is optional, implicitly return with the optional value. rethrow_expr ::= expr \"!\"","title":"Rethrow expression"},{"location":"specification/#the-expression-to-rethrow","text":"The expression must have an optional type, otherwise this is a compile time error.","title":"The expression to rethrow"},{"location":"specification/#type","text":"The type of \"rethrow\" is the inner expr type without optional.","title":"Type"},{"location":"specification/#relational-expression","text":"rel_group_expr ::= add_group_expr | relational_expr relational_expr ::= rel_group_expr relational_op add_group_expr relational_op ::= \"<\" | \">\" | \"<=\" | \">=\" TODO","title":"Relational expression"},{"location":"specification/#and-expression","text":"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","title":"And expression"},{"location":"specification/#type_1","text":"The type of the and-expression is bool .","title":"Type"},{"location":"specification/#or-expression","text":"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","title":"Or expression"},{"location":"specification/#type_2","text":"The type of the or-expression is bool .","title":"Type"},{"location":"specification/#casts","text":"","title":"Casts"},{"location":"specification/#pointer-casts","text":"","title":"Pointer casts"},{"location":"specification/#integer-to-pointer-cast","text":"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.","title":"Integer to pointer cast"},{"location":"specification/#pointer-to-integer-cast","text":"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","title":"Pointer to integer cast"},{"location":"specification/#subscript-operator","text":"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.","title":"Subscript operator"},{"location":"specification/#operands","text":"","title":"Operands"},{"location":"specification/#compound-literals","text":"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.","title":"Compound Literals"},{"location":"specification/#function-calls","text":"","title":"Function calls"},{"location":"specification/#varargs","text":"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.","title":"Varargs"},{"location":"specification/#statements","text":"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","title":"Statements"},{"location":"specification/#asm-block-statement","text":"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","title":"Asm block statement"},{"location":"specification/#assert-statement","text":"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)*","title":"Assert statement"},{"location":"specification/#conditional-inclusion","text":"assert statements are only included in \"safe\" builds. They may turn into assume directives for the compiler on \"fast\" builds.","title":"Conditional inclusion"},{"location":"specification/#assert-message","text":"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.","title":"Assert message"},{"location":"specification/#panic-function","text":"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.","title":"Panic function"},{"location":"specification/#break-statement","text":"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? \";\"","title":"Break statement"},{"location":"specification/#break-labels","text":"If a break has a label, then it will instead exit an outer scope with the label.","title":"Break labels"},{"location":"specification/#unreachable-code","text":"Any statement following break in the same scope is considered unreachable.","title":"Unreachable code"},{"location":"specification/#compile-time-echo-statement","text":"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 \";\"","title":"Compile time echo statement"},{"location":"specification/#the-message","text":"The message must be a compile time constant string.","title":"The message"},{"location":"specification/#compile-time-assert-statement","text":"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) \";\"","title":"Compile time assert statement"},{"location":"specification/#evaluated-expression","text":"The checked expression must evaluate to a boolean compile time constant.","title":"Evaluated expression"},{"location":"specification/#error-message","text":"The second parameter, which is optional, must evaluate to a constant string.","title":"Error message"},{"location":"specification/#compile-time-error-statement","text":"During parsing, when semantically checked this statement will output a compile time error with the message given. ct_error_stmt ::= \"$error\" constant_expr \";\"","title":"Compile time error statement"},{"location":"specification/#error-message_1","text":"The parameter must evaluate to a constant string.","title":"Error message"},{"location":"specification/#compile-time-if-statement","text":"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\"","title":"Compile time if statement"},{"location":"specification/#cond-expression","text":"The cond expression must be possible to evaluate to true or false at compile time.","title":"Cond expression"},{"location":"specification/#scopes","text":"The \"then\" and \"else\" branches will add a compile time scope that is exited when reaching $endif . It adds no runtime scope.","title":"Scopes"},{"location":"specification/#evaluation","text":"Statements in the branch not picked will not be semantically checked.","title":"Evaluation"},{"location":"specification/#compile-time-switch-statement","text":"ct_switch_stmt ::= \"$switch\" (\"(\" ct_expr_or_type \")\")? ct_case_stmt+ \"$endswitch\" ct_case_stmt ::= (\"$default\" | \"$case\" ct_expr_or_type) \":\" stmt*","title":"Compile time switch statement"},{"location":"specification/#no-cond-expression-switch","text":"If the cond expression is missing, evaluation will go through each case until one case expression evaluates to true.","title":"No cond expression switch"},{"location":"specification/#type-expressions","text":"If a cond expression is a type, then all case statement expressions must be types as well.","title":"Type expressions"},{"location":"specification/#ranged-cases","text":"Compile time switch does not support ranged cases.","title":"Ranged cases"},{"location":"specification/#fallthrough","text":"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.","title":"Fallthrough"},{"location":"specification/#break-and-nextcase","text":"Compile time switches do not support break nor nextcase .","title":"Break and nextcase"},{"location":"specification/#evaluation-of-statements","text":"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.","title":"Evaluation of statements"},{"location":"specification/#continue-statement","text":"A continue statement jumps to the cond expression of a while , for , do or foreach continue_stmt ::= \"continue\" label? \";\"","title":"Continue statement"},{"location":"specification/#continue-labels","text":"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.","title":"Continue labels"},{"location":"specification/#unreachable-code_1","text":"Any statement following continue in the same scope is considered unreachable.","title":"Unreachable code"},{"location":"specification/#declaration-statement","text":"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)?","title":"Declaration statement"},{"location":"specification/#thread-local-storage","text":"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.","title":"Thread local storage"},{"location":"specification/#static-storage","text":"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.","title":"Static storage"},{"location":"specification/#scopes_1","text":"Runtime variables are added to the runtime scope, compile time variables to the compile time scope. See var statements .","title":"Scopes"},{"location":"specification/#multiple-declarations","text":"If more than one variable is declared, no init expressions are allowed for any of the variables.","title":"Multiple declarations"},{"location":"specification/#no-init-expression","text":"If no init expression is provided, the variable is zero initialized .","title":"No init expression"},{"location":"specification/#opt-out-of-zero-initialization","text":"Using the @noinit attribute opts out of zero initialization .","title":"Opt-out of zero initialization"},{"location":"specification/#self-referencing-initialization","text":"An init expression may refer to the address of the same variable that is declared, but not the value of the variable. Example: void* a = &a; // Valid int a = a + 1; // Invalid","title":"Self referencing initialization"},{"location":"specification/#defer-statement","text":"The defer statements are executed at (runtime) scope exit, whether through return , break , continue or rethrow. defer_stmt ::= \"defer\" (\"try\" | \"catch\")? stmt","title":"Defer statement"},{"location":"specification/#defer-in-defer","text":"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.","title":"Defer in defer"},{"location":"specification/#static-and-tlocal-variables-in-defer","text":"Static and tlocal variables are allowed in a defer statement. Only a single variable is instantiated regardless of the number of inlining locations.","title":"Static and tlocal variables in defer"},{"location":"specification/#defer-and-return","text":"If the return has an expression, then it is evaluated before the defer statements (due to exit from the current function scope), are executed. Example: int a = 0; defer a++; return a; // This is equivalent to int a = 0; int temp = a; a++; return temp;","title":"Defer and return"},{"location":"specification/#defer-and-jump-statements","text":"A defer body may not contain a break , continue , return or rethrow that would exit the statement.","title":"Defer and jump statements"},{"location":"specification/#defer-execution","text":"Defer statements are executed in the reverse order of their declaration, starting from the last declared defer statement.","title":"Defer execution"},{"location":"specification/#defer-try","text":"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.","title":"Defer try"},{"location":"specification/#defer-catch","text":"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.","title":"Defer catch"},{"location":"specification/#non-regular-returns-longjmp-panic-and-other-errors","text":"Defers will not execute when doing longjmp terminating through a panic or other error. They are only invoked on regular scope exits.","title":"Non-regular returns - longjmp, panic and other errors"},{"location":"specification/#expr-statement","text":"An expression statement evaluates an expression. expr_stmt ::= expr \";\"","title":"Expr statement"},{"location":"specification/#no-discard","text":"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.","title":"No discard"},{"location":"specification/#if-statement","text":"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)","title":"If statement"},{"location":"specification/#scopes_2","text":"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.","title":"Scopes"},{"location":"specification/#special-parsing-of-the-then-clause","text":"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.","title":"Special parsing of the \"then\" clause"},{"location":"specification/#break","text":"It is possible to use labelled break to break out of an if statement. Note that an unlabelled break may not be used.","title":"Break"},{"location":"specification/#if-try","text":"The cond expression may be a try-unwrap chain. In this case, the unwrapped variables are scoped to the \"then\" clause only.","title":"If-try"},{"location":"specification/#if-catch","text":"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. }","title":"If-catch"},{"location":"specification/#if-catch-implicit-unwrap","text":"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.","title":"If-catch implicit unwrap"},{"location":"specification/#nextcase-statement","text":"Nextcase will jump to another switch case. nextcase_stmt ::= \"nextcase\" ((label \":\")? (expr | \"default\"))? \";\"","title":"Nextcase statement"},{"location":"specification/#labels","text":"When a nextcase has a label, the jump is to the switch in an outer scope with the corresponding label.","title":"Labels"},{"location":"specification/#no-expression-jumps","text":"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.","title":"No expression jumps"},{"location":"specification/#jumps-to-default","text":"Using default jumps to the default clause of a switch.","title":"Jumps to default"},{"location":"specification/#missing-case","text":"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.","title":"Missing case"},{"location":"specification/#variable-expression","text":"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.","title":"Variable expression"},{"location":"specification/#unreachable-code_2","text":"Any statement in the same scope after a nextcase are considered unreachable .","title":"Unreachable code"},{"location":"specification/#switch-statement","text":"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*","title":"Switch statement"},{"location":"specification/#regular-switch","text":"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.","title":"Regular switch"},{"location":"specification/#if-switch","text":"If the cond expression is missing or the case statements are non-constant expressions, then each case clause will be evaluated in order after the cond expression has been evaluated (if it exists): If a cond expression exists, calculate the case expression and execute the case if it is matching the cond expression. A default statement has no expression and will always be considered matching the cond expression reached. If no con expression exists, calculate the case expression and execute the case if the expression evaluates to \"true\" when implicitly converted to boolean. A default statement will always be considered having the \"true\" result.","title":"If-switch"},{"location":"specification/#any-switch","text":"If the cond expression is an any* type, the switch is handled as if switching was done over the type field of the any* . This field has the type of typeid , and the cases follows the rules for switching over typeid . If the cond expression is a variable, then this variable is implicitly converted to a pointer with the pointee type given by the case statement. Example: any* a = abc(); switch (a) { case int: int b = *a; // a is int* case float: float z = *a; // a is float* case Bar: Bar f = *a; // a is Bar* default: // a is not unwrapped }","title":"Any-switch"},{"location":"specification/#ranged-cases_1","text":"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.","title":"Ranged cases"},{"location":"specification/#fallthrough_1","text":"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.","title":"Fallthrough"},{"location":"specification/#exhaustive-switch","text":"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.","title":"Exhaustive switch"},{"location":"specification/#break_1","text":"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.","title":"Break"},{"location":"specification/#unreachable-code_3","text":"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 .","title":"Unreachable code"},{"location":"specification/#switching-over-typeid","text":"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 .","title":"Switching over typeid"},{"location":"specification/#nextcase-without-expression","text":"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.","title":"Nextcase without expression"},{"location":"specification/#nextcase-with-expression","text":"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.","title":"Nextcase with expression"},{"location":"specification/#do-statement","text":"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 \")\")? \";\"","title":"Do statement"},{"location":"specification/#unreachable-code_4","text":"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.","title":"Unreachable code"},{"location":"specification/#break_2","text":"break will exit the do with execution continuing on the following statement.","title":"Break"},{"location":"specification/#continue","text":"continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached.","title":"Continue"},{"location":"specification/#do-block","text":"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\"","title":"Do block"},{"location":"specification/#for-statement","text":"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?","title":"For statement"},{"location":"specification/#init-expression","text":"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.","title":"Init expression"},{"location":"specification/#incr-expression","text":"The incr expression is evaluated before evaluating the cond expr every time except for the first one. The incr expression may optionally be omitted.","title":"Incr expression"},{"location":"specification/#cond-expression_1","text":"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 .","title":"Cond expression"},{"location":"specification/#unreachable-code_5","text":"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.","title":"Unreachable code"},{"location":"specification/#break_3","text":"break will exit the for with execution continuing on the following statement after the for .","title":"Break"},{"location":"specification/#continue_1","text":"continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached.","title":"Continue"},{"location":"specification/#equivalence-of-while-and-for","text":"A while loop is functionally equivalent to a for loop without init and incr expressions.","title":"Equivalence of while and for"},{"location":"specification/#foreach-and-foreach_r-statements","text":"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","title":"Foreach and foreach_r statements"},{"location":"specification/#break_4","text":"break will exit the foreach statement with execution continuing on the following statement after.","title":"Break"},{"location":"specification/#continue_2","text":"continue will cause the next iteration to commence, as if the end of the statement had been reached.","title":"Continue"},{"location":"specification/#iteration-by-value-or-reference","text":"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.","title":"Iteration by value or reference"},{"location":"specification/#foreach-variable","text":"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.","title":"Foreach variable"},{"location":"specification/#foreach-index","text":"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.","title":"Foreach index"},{"location":"specification/#foreach-support","text":"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.","title":"Foreach support"},{"location":"specification/#return-statement","text":"The return statement evaluates its expression (if present) and returns the result. return_stmt ::= \"return\" expr? \";\"","title":"Return statement"},{"location":"specification/#jumps-in-return-statements","text":"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;","title":"Jumps in return statements"},{"location":"specification/#return-from-expression-blocks","text":"A return from an expression block only returns out of the expression block, it never returns from the expression block's enclosing scope.","title":"Return from expression blocks"},{"location":"specification/#empty-returns","text":"An empty return is equivalent to a return with a void type. Consequently constructs like foo(); return; and return (void)foo(); are equivalent.","title":"Empty returns"},{"location":"specification/#unreachable-code_6","text":"Any statement directly following a return in the same scope are considered unreachable.","title":"Unreachable code"},{"location":"specification/#while-statement","text":"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","title":"While statement"},{"location":"specification/#unreachable-code_7","text":"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.","title":"Unreachable code"},{"location":"specification/#break_5","text":"break will exit the while with execution continuing on the following statement.","title":"Break"},{"location":"specification/#continue_3","text":"continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached.","title":"Continue"},{"location":"specification/#var-statement","text":"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)? \";\"","title":"Var statement"},{"location":"specification/#inferring-type","text":"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.","title":"Inferring type"},{"location":"specification/#scope","text":"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 ).","title":"Scope"},{"location":"specification/#attributes","text":"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","title":"Attributes"},{"location":"specification/#user-defined-attributes","text":"User defined attributes group a list of attributes. attribute_decl ::= \"def\" AT_TYPE_IDENT (\"(\" parameters \")\")? attribute* \"=\" \"{\" attribute* \"}\" \";\"","title":"User defined attributes"},{"location":"specification/#empty-list-of-attributes","text":"The list of attributes may be empty.","title":"Empty list of attributes"},{"location":"specification/#parameter-arguments","text":"Arguments given to user defined attributes will be passed on to the attributes in the list.","title":"Parameter arguments"},{"location":"specification/#expansion","text":"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.","title":"Expansion"},{"location":"specification/#nesting","text":"A user defined attribute can contain other user defined attributes. The definition may not be cyclic.","title":"Nesting"},{"location":"specification/#modules","text":"Module paths are hierarchal, with each sub-path appended with '::' + the name: path ::= PATH_SEGMENT (\"::\" PATH_SEGMENT) Each module declaration starts its own module section . All imports and all @local declarations are only visible in the current module section . module_section ::= \"module\" path opt_generic_params? attributes? \";\" generic_param ::= TYPE_IDENT | CONST_IDENT opt_generic_params ::= \"(<\" generic_param (\",\" generic_param)* \">)\" Any visibility attribute defined in a module section will be the default visibility in all declarations in the section. If the @benchmark attribute is applied to the module section then all function declarations will implicitly have the @benchmark attribute. If the @test attribute is applied to the module section then all function declarations will implicitly have the @test attribute.","title":"Modules"},{"location":"standard_library/","text":"Library The standard library is currently in development, so frequent changes will occur. Note that all std::core modules and sub modules are implicitly imported. std::core::builtin All functions and macros in this library can be used without path qualifiers. void panic(char message, char file, char *function, uint line) Default function called when the asserts fails. void @swap(&a, &b) Swap values in a and b . int a = 3; int b = 5; @swap(a, b); io::printfn(\"%d\", a); // Prints 5 varcast(any* v, $Type) Optionally cast the value v to type $Type* on failure returns VarCastResult.TYPE_MISMATCH . 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. switch (x) { case 0: foo(); case 1: bar(); default: // Should never happen. unreachable(\"x should have been 0 or 1\"); } On safe mode this will throw a runtime panic when reached. For release mode the compiler will assume this case never happens. bitcast(value, $Type) Do a bitcast of a value to $Type , requires that the types are of the same memory size. float x = 1.0; int y = bitcast(x, int); // y = 0x3f800000 enum_by_name($Type, enum_name) Optionally returns the enum value with the given name. $Type must be an enum. Returns SearchResult.MISSING on failure. enum Foo { ABC, CDE, EFG } fn void! test() { Foo f = enum_by_name(Foo, \"CDE\")!; // same as Foo f = Foo.CDE; } void @scope(&variable; @body) Scopes a variable: int a = 3; @scope(a) { a = 4; a++; }; // Prints a = 3 io::printfn(\"a = %d\", a, b); less, greater, less_eq, greater_eq, equals All macros take two values and compare them. Any type implementing Type.less or Type.compare_to may be compared (or if the type implements < ). Types implementing Type.equals may use equals even if neither less nor compare_to are implemented. Faults IteratorResult returned when reaching the end of an iterator. SearchResult used when a search fails. 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. 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. int* int = malloc(int); int[] ints = new_array(int, 100); // Allocated int[100] on the heap. 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. 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. @poo() { // This allocation uses the dynamic arena Foo* f = talloc(Foo); }; @volatile_load(&x) Returns the value in x using a volatile load. // Both loads will always happen: int y = @volatile_load(my_global); y = @volatile_load(my_global); @volatile_store(&x, y) Store the value y in x using a volatile store. // Both stores will always happen: @volatile_store(y, 1); @volatile_store(y, 1); usz aligned_offset(usz offset, usz alignment) Returns an aligned size based on the current offset. The alignment must be a power of two. E.g. mem::aligned_offset(17, 8) would return 24 usz aligned_pointer(void* ptr, usz alignment) Returns a pointer aligned to the given alignment, using aligned_offset . bool ptr_is_aligned(void* ptr, usz alignment) Return true if the pointer is aligned, false otherwise. void copy(void dst, void src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false) Copies bytes from one pointer to another. It may optionally be set as volatile, in which case the copy may not be optimized away. Furthermore the source and destination alignment may be used. Foo* f = 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. 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. 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 . 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.","title":"Standard Library"},{"location":"standard_library/#library","text":"The standard library is currently in development, so frequent changes will occur. Note that all std::core modules and sub modules are implicitly imported.","title":"Library"},{"location":"standard_library/#stdcorebuiltin","text":"All functions and macros in this library can be used without path qualifiers.","title":"std::core::builtin"},{"location":"standard_library/#void-panicchar-message-char-file-char-function-uint-line","text":"Default function called when the asserts fails.","title":"void panic(char message, char file, char *function, uint line)"},{"location":"standard_library/#void-swapa-b","text":"Swap values in a and b . int a = 3; int b = 5; @swap(a, b); io::printfn(\"%d\", a); // Prints 5","title":"void @swap(&a, &b)"},{"location":"standard_library/#varcastany-v-type","text":"Optionally cast the value v to type $Type* on failure returns VarCastResult.TYPE_MISMATCH . int b; any* a = &b; float*! c = varcast(a, float); // Will return TYPE_MISMATCH int*! d = varcast(a, int); // Works!","title":"varcast(any* v, $Type)"},{"location":"standard_library/#void-unreachablestring-unreachable-statement-reached","text":"Mark a code path as unreachable. switch (x) { case 0: foo(); case 1: bar(); default: // Should never happen. unreachable(\"x should have been 0 or 1\"); } On safe mode this will throw a runtime panic when reached. For release mode the compiler will assume this case never happens.","title":"void unreachable($string = \"Unreachable statement reached.\")"},{"location":"standard_library/#bitcastvalue-type","text":"Do a bitcast of a value to $Type , requires that the types are of the same memory size. float x = 1.0; int y = bitcast(x, int); // y = 0x3f800000","title":"bitcast(value, $Type)"},{"location":"standard_library/#enum_by_nametype-enum_name","text":"Optionally returns the enum value with the given name. $Type must be an enum. Returns SearchResult.MISSING on failure. enum Foo { ABC, CDE, EFG } fn void! test() { Foo f = enum_by_name(Foo, \"CDE\")!; // same as Foo f = Foo.CDE; }","title":"enum_by_name($Type, enum_name)"},{"location":"standard_library/#void-scope-body","text":"Scopes a variable: int a = 3; @scope(a) { a = 4; a++; }; // Prints a = 3 io::printfn(\"a = %d\", a, b);","title":"void @scope(&variable; @body)"},{"location":"standard_library/#less-greater-less_eq-greater_eq-equals","text":"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.","title":"less, greater, less_eq, greater_eq, equals"},{"location":"standard_library/#faults","text":"IteratorResult returned when reaching the end of an iterator. SearchResult used when a search fails. AnyCastResult when a any cast fails.","title":"Faults"},{"location":"standard_library/#stdcoreenv","text":"","title":"std::core::env"},{"location":"standard_library/#constants","text":"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.","title":"Constants"},{"location":"standard_library/#stdcoremem","text":"","title":"std::core::mem"},{"location":"standard_library/#malloc-malloc_checked-malloc_aligned","text":"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. 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);","title":"malloc, malloc_checked, malloc_aligned"},{"location":"standard_library/#newtype-new_arraytype-usz-elements","text":"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. int* int = malloc(int); int[] ints = new_array(int, 100); // Allocated int[100] on the heap. 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);","title":"new($Type), new_array($Type, usz elements)"},{"location":"standard_library/#calloc-calloc_checked-calloc_aligned","text":"Identical to the malloc variants, except the data is guaranteed to be zeroed out.","title":"calloc, calloc_checked, calloc_aligned"},{"location":"standard_library/#relloc-relloc_checked-realloc_aligned","text":"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 .","title":"relloc, relloc_checked, realloc_aligned"},{"location":"standard_library/#free-free_aligned","text":"Frees memory allocated using malloc or calloc . Any memory allocated using \"_aligned\" variants must be freed using free_aligned .","title":"free, free_aligned"},{"location":"standard_library/#scopedallocator-allocator-body","text":"Swaps the current memory allocator for the duration of the call. DynamicArenaAllocator dynamic_arena; dynamic_arena.init(1024); mem::@scoped(&dynamic_arena) { // This allocation uses the dynamic arena Foo* f = malloc(Foo); }; // Release any dynamic arena memory. dynamic_arena.destroy();","title":"@scoped(Allocator* allocator; @body())"},{"location":"standard_library/#tscoped-body","text":"Same as @scoped, but uses the temporary allocator rather than any arbitrary allocator.","title":"@tscoped(; @body())"},{"location":"standard_library/#void-tmallocusz-size-usz-alignment-0","text":"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.","title":"void* tmalloc(usz size, usz alignment = 0)"},{"location":"standard_library/#void-tcallocusz-size-usz-alignment-0","text":"Same as tmalloc but clears the memory.","title":"void* tcalloc(usz size, usz alignment = 0)"},{"location":"standard_library/#void-treallocvoid-ptr-usz-size-usz-alignment-0","text":"realloc but on memory received using tcalloc or tmalloc .","title":"void trealloc(void ptr, usz size, usz alignment = 0)"},{"location":"standard_library/#void-poolbody","text":"Opens a temporary memory scope. @poo() { // This allocation uses the dynamic arena Foo* f = talloc(Foo); };","title":"void @pool(;@body)"},{"location":"standard_library/#volatile_loadx","text":"Returns the value in x using a volatile load. // Both loads will always happen: int y = @volatile_load(my_global); y = @volatile_load(my_global);","title":"@volatile_load(&x)"},{"location":"standard_library/#volatile_storex-y","text":"Store the value y in x using a volatile store. // Both stores will always happen: @volatile_store(y, 1); @volatile_store(y, 1);","title":"@volatile_store(&x, y)"},{"location":"standard_library/#usz-aligned_offsetusz-offset-usz-alignment","text":"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","title":"usz aligned_offset(usz offset, usz alignment)"},{"location":"standard_library/#usz-aligned_pointervoid-ptr-usz-alignment","text":"Returns a pointer aligned to the given alignment, using aligned_offset .","title":"usz aligned_pointer(void* ptr, usz alignment)"},{"location":"standard_library/#bool-ptr_is_alignedvoid-ptr-usz-alignment","text":"Return true if the pointer is aligned, false otherwise.","title":"bool ptr_is_aligned(void* ptr, usz alignment)"},{"location":"standard_library/#void-copyvoid-dst-void-src-usz-len-usz-dst_align-0-usz-src_align-0-bool-is_volatile-false","text":"Copies bytes from one pointer to another. It may optionally be set as volatile, in which case the copy may not be optimized away. Furthermore the source and destination alignment may be used. Foo* f = talloc(data_size); mem::copy(f, slice.ptr, size);","title":"void copy(void dst, void src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)"},{"location":"standard_library/#void-setvoid-dst-char-val-usz-len-usz-dst_align-0-bool-is_volatile-false","text":"Sets bytes to a value. This operation may be aligned and/or volatile. See the copy method.","title":"void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false)"},{"location":"standard_library/#void-clearvoid-dst-usz-len-usz-dst_align-0-bool-is_volatile-false","text":"Sets bytes to zero. This operation may be aligned and/or volatile. See the copy method.","title":"void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false)"},{"location":"standard_library/#clonevalue","text":"Makes a shallow copy of a value using the regular allocator. Foo f = ... return @clone(f);","title":"@clone(&value)"},{"location":"standard_library/#tclonevalue","text":"Same as @clone but uses the temporary allocator.","title":"@tclone(&value)"},{"location":"standard_library/#stdcoretypes","text":"","title":"std::core::types"},{"location":"standard_library/#bool-is_comparabletype","text":"Return true if the type can be used with comparison operators.","title":"bool is_comparable($Type)"},{"location":"standard_library/#bool-is_equatable_valuevalue","text":"Return true if the value can be compared using the equals macro.","title":"bool is_equatable_value(value)"},{"location":"standard_library/#bool-is_equatable_valuevalue_1","text":"Return true if the value can be compared using the comparison macros.","title":"bool is_equatable_value(value)"},{"location":"standard_library/#kind_is_inttypekind-kind","text":"","title":"kind_is_int(TypeKind kind)"},{"location":"standard_library/#any_to_intany-v-type","text":"Returns an optional value of $Type if the any value losslessly may be converted into the given type. Returns a ConversionResult otherwise. any* v = &&128; short y = any_to_int(v, short)!!; // Works ichar z = any_to_int(v, ichar)!!; // Panics VALUE_OUT_OF_RANGE","title":"any_to_int(any* v, $Type)"},{"location":"standard_library/#stdcorestrconv","text":"","title":"std::core::str::conv"},{"location":"standard_library/#usz-char32_to_utf8char32-c-char-output-usz-available","text":"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.","title":"usz! char32_to_utf8(Char32 c, char* output, usz available)"},{"location":"standard_library/#void-char32_to_utf16_unsafechar32-c-char16-output","text":"Convert a UTF32 codepoint to an UTF16 buffer without bounds checking, moving the output pointer 1 or 2 steps.","title":"void char32_to_utf16_unsafe(Char32 c, Char16** output)"},{"location":"standard_library/#stdio","text":"","title":"std::io"},{"location":"standard_library/#usz-printfstring-format-args-maydiscard","text":"Regular printf functionality: %s , %x , %d , %f and %p are supported. Will also print enums and vectors.","title":"usz! printf(String format, args...) @maydiscard"},{"location":"standard_library/#usz-dstringappendfdstring-str-string-format-args-maydiscard","text":"Same as printf but on dynamic strings.","title":"usz! DString.appendf(DString* str, String format, args...) @maydiscard"},{"location":"standard_library/#usz-fileprintffile-file-string-format-args-maydiscard","text":"Same as printf but on files.","title":"usz! File.printf(File file, String format, args...) @maydiscard"},{"location":"standard_library/#void-fileopenfile-file-string-filename-string-mode","text":"Open a file with the given file name with the given mode (r, w etc)","title":"void! File.open(File* file, String filename, String mode)"},{"location":"standard_library/#void-fileseekfile-file-long-offset-seek-seekmode-seekset","text":"Seek in a file. Based on the libc function.","title":"void! File.seek(File *file, long offset, Seek seekMode = Seek.SET)"},{"location":"standard_library/#void-fileclosefile-file-inline","text":"Close a file, based on the libc function.","title":"void! File.close(File *file) @inline"},{"location":"standard_library/#bool-fileeoffile-file-inline","text":"True if EOF has been reached. Based on the libc function.","title":"bool File.eof(File* file) @inline"},{"location":"standard_library/#void-fileputcfile-file-char-c","text":"Write a single byte to a file. See the libc function.","title":"void! File.putc(File *file, char c)"},{"location":"standard_library/#usz-filereadfile-file-void-buffer-usz-items-usz-element_size-1","text":"Read into a buffer, based on the libc function.","title":"usz File.read(File file, void buffer, usz items, usz element_size = 1)"},{"location":"standard_library/#usz-filewritefile-file-void-buffer-usz-items-usz-element_size-1","text":"Write to a buffer, based on the libc function.","title":"usz File.write(File file, void buffer, usz items, usz element_size = 1)"},{"location":"standard_library/#stdout-stdin-stderr","text":"Return stdout, stdin and stderr respectively.","title":"stdout(), stdin(), stderr()"},{"location":"standard_library/#stdcollectionslist","text":"Generic list module, elements are of Type . import std::collections::list; def MyIntList = List(); ... MyIntList list; list.push(123); list.free();","title":"std::collections::list()"},{"location":"standard_library/#listpushlist-list-type-element-append","text":"Append a single value to the list.","title":"List.push(List *list, Type element), append(...)"},{"location":"standard_library/#type-listpoplist-list","text":"Removes and returns the last entry in the list.","title":"Type List.pop(List* list)"},{"location":"standard_library/#type-listpop_firstlist-list","text":"Removes the first entry in the list.","title":"Type List.pop_first(List *list)"},{"location":"standard_library/#void-listremove_atlist-list-usz-index","text":"Removes the entry at index .","title":"void List.remove_at(List *list, usz index)"},{"location":"standard_library/#void-listinsert_atlist-list-usz-index-type-type","text":"Inserts a value at index .","title":"void List.insert_at(List *list, usz index, Type type)"},{"location":"standard_library/#void-listpush_frontlist-list-type-type","text":"Inserts a value to the front of the list.","title":"void List.push_front(List *list, Type type)"},{"location":"standard_library/#void-listremove_lastlist-list","text":"Remove the last value of the list.","title":"void List.remove_last(List* list)"},{"location":"standard_library/#void-listremove_firstlist-list","text":"Remove the first element in the list.","title":"void List.remove_first(List *list)"},{"location":"standard_library/#type-listfirstlist-list","text":"Return the first element in the list if available.","title":"Type List.first(List list)"},{"location":"standard_library/#type-listlastlist-list","text":"Return the last element in the list if available.","title":"Type List.last(List list)"},{"location":"standard_library/#listis_emptylist-list","text":"Return true if the list is empty.","title":"List.is_empty(List *list)"},{"location":"standard_library/#usz-listlenlist-list","text":"Return the number of elements in the list.","title":"usz List.len(List *list)"},{"location":"standard_library/#type-listgetlist-list-usz-index","text":"Return the value at index .","title":"Type List.get(List *list, usz index)"},{"location":"standard_library/#void-listfreelist-list","text":"Free all memory associated with this list.","title":"void List.free(List *list)"},{"location":"standard_library/#void-listswaplist-list-usz-i-usz-j","text":"Swap two elements in the list.","title":"void List.swap(List *list, usz i, usz j)"},{"location":"statements/","text":"Statements 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(); }","title":"Statements"},{"location":"statements/#statements","text":"Statements largely work like in C, but with some additions.","title":"Statements"},{"location":"statements/#expression-blocks","text":"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); }","title":"Expression blocks"},{"location":"statements/#labelled-break-and-continue","text":"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; } } }","title":"Labelled break and continue"},{"location":"statements/#do-without-while","text":"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\"); };","title":"Do-without-while"},{"location":"statements/#nextcase-and-labelled-nextcase","text":"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.","title":"Nextcase and labelled nextcase"},{"location":"statements/#switch-cases-with-runtime-evaluation","text":"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(); }","title":"Switch cases with runtime evaluation"},{"location":"syntax/","text":"Syntax 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 %{ #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; }","title":"Grammar"},{"location":"syntax/#syntax","text":"","title":"Syntax"},{"location":"syntax/#keywords","text":"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","title":"Keywords"},{"location":"syntax/#yacc-grammar","text":"%{ #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; }","title":"Yacc grammar"},{"location":"test/","text":"Four ways to ways when you need a variably sized list in C3 In this blog post we'll review four standard ways to handle the case when you need a list with a size which is only known at runtime. Use a generic List allocated on the heap import std::io, std::collections::list; // We create a generic List that holds doubles: def DoubleList = List(); fn double test_list_on_heap(int len) { DoubleList list; // By default will allocate on the heap defer list.free(); // Free memory at exit with a defer statement. for (int i = 0; i < len; i++) { // Append each element list.push(i + 1.0); } double sum = 0; foreach (d : list) sum += d; return sum; } We can use list.init_new(len) if we have some default length in mind, otherwise it's not necessary. Use a generic List allocated with the temp allocator Here we instead use the temp allocator to allocate and manage memory. The @pool() { ... } construct will release all temporary allocations inside of the body block. fn double test_list_on_temp_allocator(int len) { @pool() { DoubleList list; list.init_temp(); // Init using the temp allocator for (int i = 0; i < len; i++) { list.push(i + 1.0); } double sum = 0; foreach (d : list) sum += d; // No need to free explicitly! return sum; }; } Allocate an array on the heap This is the conventional way to do it in C if the length is unknown. Note how we can use defer to write the allocation and the free together to avoid forgetting freeing if there are multiple exits. fn double test_array_on_heap(int len) { double[] arr = malloc(double, len); defer free(arr); // Free at function exit. for (int i = 0; i < len; i++) { arr[i] = i + 1.0; } double sum = 0; foreach (d : arr) sum += d; return sum; } Allocate an array on the temp allocator Using the temp allocator is as close to doing allocations for free if we need arbitrarily long lists: fn double test_array_on_temp_allocator(int len) { @pool() { // The array will be released when exiting `pool()` double[] arr = talloc(double, len); for (int i = 0; i < len; i++) { arr[i] = i + 1.0; } double sum = 0; foreach (d : arr) sum += d; return sum; }; } Summary We looked at four standard ways to use arbitrarily long lists in C3. Two of them used a growable list, which is important if you might not know the exact length in advance. The other two use simple arrays. This also contrasted using the temp allocator with the heap allocator. In a later blog post I'll discuss the allocators in more detail. A gist with the full code can be found here .","title":"Test"},{"location":"test/#use-a-generic-list-allocated-on-the-heap","text":"import std::io, std::collections::list; // We create a generic List that holds doubles: def DoubleList = List(); fn double test_list_on_heap(int len) { DoubleList list; // By default will allocate on the heap defer list.free(); // Free memory at exit with a defer statement. for (int i = 0; i < len; i++) { // Append each element list.push(i + 1.0); } double sum = 0; foreach (d : list) sum += d; return sum; } We can use list.init_new(len) if we have some default length in mind, otherwise it's not necessary.","title":"Use a generic List allocated on the heap"},{"location":"test/#use-a-generic-list-allocated-with-the-temp-allocator","text":"Here we instead use the temp allocator to allocate and manage memory. The @pool() { ... } construct will release all temporary allocations inside of the body block. fn double test_list_on_temp_allocator(int len) { @pool() { DoubleList list; list.init_temp(); // Init using the temp allocator for (int i = 0; i < len; i++) { list.push(i + 1.0); } double sum = 0; foreach (d : list) sum += d; // No need to free explicitly! return sum; }; }","title":"Use a generic List allocated with the temp allocator"},{"location":"test/#allocate-an-array-on-the-heap","text":"This is the conventional way to do it in C if the length is unknown. Note how we can use defer to write the allocation and the free together to avoid forgetting freeing if there are multiple exits. fn double test_array_on_heap(int len) { double[] arr = malloc(double, len); defer free(arr); // Free at function exit. for (int i = 0; i < len; i++) { arr[i] = i + 1.0; } double sum = 0; foreach (d : arr) sum += d; return sum; }","title":"Allocate an array on the heap"},{"location":"test/#allocate-an-array-on-the-temp-allocator","text":"Using the temp allocator is as close to doing allocations for free if we need arbitrarily long lists: fn double test_array_on_temp_allocator(int len) { @pool() { // The array will be released when exiting `pool()` double[] arr = talloc(double, len); for (int i = 0; i < len; i++) { arr[i] = i + 1.0; } double sum = 0; foreach (d : arr) sum += d; return sum; }; }","title":"Allocate an array on the temp allocator"},{"location":"test/#summary","text":"We looked at four standard ways to use arbitrarily long lists in C3. Two of them used a growable list, which is important if you might not know the exact length in advance. The other two use simple arrays. This also contrasted using the temp allocator with the heap allocator. In a later blog post I'll discuss the allocators in more detail. A gist with the full code can be found here .","title":"Summary"},{"location":"types/","text":"Types 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 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 \u2013 where xx is the bit width for typed integers, e.g. 1234u16 Integers may be written in decimal, but also in binary with the prefix 0b e.g. 0b0101000111011 , 0b011 in octal with the prefix 0o e.g. 0o0770 , 0o12345670 in hexadecimal with the prefix 0x e.g. 0xdeadbeef 0x7f7f7f In the case of binary, octal and hexadecimal, the type is assumed to be unsigned . Furthermore, underscore _ may be used to add space between digits to improve readability e.g. 0xFFFF_1234_4511_0000 , 123_000_101_100 TwoCC, FourCC and EightCC FourCC codes are often used to identify binary format types. C3 adds direct support for 4 character codes, but also 2 and 8 characters: 2 character strings, e.g. 'C3' , would convert to an ushort or short. 4 character strings, e.g. 'TEST' , converts to an uint or int. 8 character strings, e.g. 'FOOBAR11' converts to an ulong or long. Conversion is always done so that the character string has the correct ordering in memory. This means that the same characters may have different integer values on different architectures due to 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 . 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 . 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 . 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. \u2013 It's not allowed, only one level of dereference is done.) To change alignment and packing, optional 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 }","title":"Types"},{"location":"types/#types","text":"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.","title":"Types"},{"location":"types/#naming","text":"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.","title":"Naming"},{"location":"types/#differences-from-c","text":"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 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!```","title":"Differences from C"},{"location":"types/#basic-types","text":"Basic types are divided into floating point types, and integer types. Integer types being either signed or unsigned.","title":"Basic types"},{"location":"types/#integer-types","text":"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.","title":"Integer types"},{"location":"types/#integer-arithmetics","text":"All signed integer arithmetics uses 2's complement.","title":"Integer arithmetics"},{"location":"types/#integer-constants","text":"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 \u2013 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","title":"Integer constants"},{"location":"types/#twocc-fourcc-and-eightcc","text":"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.","title":"TwoCC, FourCC and EightCC"},{"location":"types/#base64-and-hex-data-literals","text":"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\";","title":"Base64 and hex data literals"},{"location":"types/#string-literals-and-raw-strings","text":"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`\\\"\";","title":"String literals, and raw strings"},{"location":"types/#floating-point-types","text":"Name bit size float16* 16 float 32 double 64 float128* 128 *support depends on platform","title":"Floating point types"},{"location":"types/#floating-point-constants","text":"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","title":"Floating point constants"},{"location":"types/#c-compatibility","text":"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.","title":"C compatibility"},{"location":"types/#other-built-in-types","text":"","title":"Other built-in types"},{"location":"types/#pointer-types","text":"Pointers mirror C: Foo* is a pointer to a Foo , while Foo** is a pointer to a pointer of Foo.","title":"Pointer types"},{"location":"types/#the-typeid-type","text":"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.","title":"The typeid type"},{"location":"types/#the-any-type","text":"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.","title":"The any* type"},{"location":"types/#array-types","text":"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 .","title":"Array types"},{"location":"types/#vector-types","text":"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 } .","title":"Vector types"},{"location":"types/#types-created-using-def","text":"","title":"Types created using def"},{"location":"types/#typedef","text":"Like in C, C3 has a \"typedef\" construct, def = def Int32 = int; def Vector2 = float[<2>]; ... Int32 a = 1; int b = a;","title":"\"typedef\""},{"location":"types/#function-pointer-types","text":"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! }","title":"Function pointer types"},{"location":"types/#distinct-types","text":"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' }","title":"Distinct types"},{"location":"types/#inline-distinct","text":"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' }","title":"Inline distinct"},{"location":"types/#generic-types","text":"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 .","title":"Generic types"},{"location":"types/#enum","text":"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 .","title":"Enum"},{"location":"types/#enum-type-inference","text":"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.","title":"Enum type inference"},{"location":"types/#enum-associated-values","text":"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();","title":"Enum associated values"},{"location":"types/#faults","text":"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.","title":"Faults"},{"location":"types/#optional-result-types","text":"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 .","title":"Optional Result Types"},{"location":"types/#struct-types","text":"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. \u2013 It's not allowed, only one level of dereference is done.) To change alignment and packing, optional attributes such as @packed may be used.","title":"Struct types"},{"location":"types/#struct-subtyping","text":"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.","title":"Struct subtyping"},{"location":"types/#union-types","text":"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 .","title":"Union types"},{"location":"types/#nested-sub-structs-unions","text":"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; } }","title":"Nested sub-structs / unions"},{"location":"types/#bitstructs","text":"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 }","title":"Bitstructs"},{"location":"undefinedbehaviour/","text":"Undefined behaviour Like C, C3 uses undefined behaviour. In contrast, C3 will trap - that is, print an error trace and abort \u2013 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.","title":"Undefined behaviour"},{"location":"undefinedbehaviour/#undefined-behaviour","text":"Like C, C3 uses undefined behaviour. In contrast, C3 will trap - that is, print an error trace and abort \u2013 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;","title":"Undefined behaviour"},{"location":"undefinedbehaviour/#list-of-undefined-behaviours","text":"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.","title":"List of undefined behaviours"},{"location":"undefinedbehaviour/#list-of-implementation-dependent-behaviours","text":"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","title":"List of implementation dependent behaviours"},{"location":"undefinedbehaviour/#list-of-undefined-behaviour-in-c-which-is-defined-in-c3","text":"","title":"List of undefined behaviour in C, which is defined in C3"},{"location":"undefinedbehaviour/#signed-integer-overflow","text":"Signed integer is always wrapped using 2s complement.","title":"Signed Integer Overflow"},{"location":"undefinedbehaviour/#modifying-the-intermediate-results-of-an-expression","text":"Behaves as if the intermediate result was stored in a variable on the stack.","title":"Modifying the intermediate results of an expression"},{"location":"variables/","text":"Variables 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.","title":"Variables"},{"location":"variables/#variables","text":"","title":"Variables"},{"location":"variables/#zero-init-by-default","text":"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.","title":"Zero init by default"},{"location":"vectors/","text":"Vectors 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.","title":"Vectors"},{"location":"vectors/#vectors","text":"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>] .","title":"Vectors"},{"location":"vectors/#arithmetics-on-vectors","text":"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.","title":"Arithmetics on vectors"},{"location":"vectors/#scalar-values","text":"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 }","title":"Scalar values"},{"location":"vectors/#additional-operations","text":"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.","title":"Additional operations"},{"location":"vectors/#swizzling","text":"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;","title":"Swizzling"},{"location":"vectors/#array-like-operations","text":"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.","title":"Array-like operations"}]} \ No newline at end of file +{"config":{"lang":["en"],"min_search_length":3,"prebuild_index":false,"separator":"[\\s\\-]+"},"docs":[{"location":"","text":"What is C3? C3 is a system programming language based on C. It is an evolution of C enabling the same paradigms and retaining the same syntax as far as possible. C3 started as an extension of the C2 language by Bas van den Berg . It has evolved significantly, not just in syntax but also in regard to error handling, macros, generics and strings. The C3 compiler can be found on github: https://github.com/c3lang/c3c . Binaries are directly downloadable for the following platforms: Windows x64 download , install instructions . Debian x64 download , install instructions . MacOS x64 download , install instructions . Official forums: Discord: invite link: https://discord.gg/qN76R87 Discourse: https://c3lang.discourse.group Last updated: Revision 2023-10-24 . Features Full C ABI compatibility Module system Generic modules Zero overhead errors Struct subtyping Semantic macro system Safe array access using sub arrays Zero cost simple gradual & opt-in pre/post conditions High level containers and string handling C to C3 conversion (for a subset of C) TODO LLVM backend Design principles Procedural \"get things done\"-type of language. Stay close to C - only change where there is a significant need. Flawless C integration. Learning C3 should be easy for a C programmer. Dare add conveniences if the value is great. Data is inert and zero is initialization. Avoid \"big ideas\". Avoid the kitchen sink language trap. Thank yous Special thank-yous to: Bas van der Berg (Author of C2 ), Jon Goodwin (Author of Cone ) and Andrey Penechko (Author of Vox ).","title":"About C3"},{"location":"#what-is-c3","text":"C3 is a system programming language based on C. It is an evolution of C enabling the same paradigms and retaining the same syntax as far as possible. C3 started as an extension of the C2 language by Bas van den Berg . It has evolved significantly, not just in syntax but also in regard to error handling, macros, generics and strings. The C3 compiler can be found on github: https://github.com/c3lang/c3c . Binaries are directly downloadable for the following platforms: Windows x64 download , install instructions . Debian x64 download , install instructions . MacOS x64 download , install instructions . Official forums: Discord: invite link: https://discord.gg/qN76R87 Discourse: https://c3lang.discourse.group Last updated: Revision 2023-10-24 .","title":"What is C3?"},{"location":"#features","text":"Full C ABI compatibility Module system Generic modules Zero overhead errors Struct subtyping Semantic macro system Safe array access using sub arrays Zero cost simple gradual & opt-in pre/post conditions High level containers and string handling C to C3 conversion (for a subset of C) TODO LLVM backend","title":"Features"},{"location":"#design-principles","text":"Procedural \"get things done\"-type of language. Stay close to C - only change where there is a significant need. Flawless C integration. Learning C3 should be easy for a C programmer. Dare add conveniences if the value is great. Data is inert and zero is initialization. Avoid \"big ideas\". Avoid the kitchen sink language trap.","title":"Design principles"},{"location":"#thank-yous","text":"Special thank-yous to: Bas van der Berg (Author of C2 ), Jon Goodwin (Author of Cone ) and Andrey Penechko (Author of Vox ).","title":"Thank yous"},{"location":"allfeatures/","text":"Feature summary Here is a summary of all the features of C3 and changes from C Symbols and literals Changes relating to literals, identifiers etc. Added 0o prefix for octal. 0b prefix for binary. Optional \"_\" as digit separator. Hexadecimal byte data, e.g x\"abcd\" . Base64 byte data, e.g. b64\"QzM=\" . Type name restrictions (PascalCase). Variable and function name restrictions (must start with lower case letter). Constant name restrictions (no lower case). Character literals may be 2, 4, 8, 16 bytes long. (2cc, 4cc etc). Raw string literals between \"`\". \\e escape character. Source code must be UTF-8. Assumes \\n for new row \\r is stripped from source. Bit-width integer and float suffixes: u8/i8/u16/i16/... f32/f64/... The null literal is a pointer value of 0. The true and false are boolean constants true and false. Removed Trigraphs / digraphs. 0123-style octal. z , LL and ULL suffixes. Built-in types Added Type declaration is left to right: int[4]*[2] a; instead of int (*a[2])[4]; Simd vector types using [<>] syntax, e.g. float[<4>] , use [<*>] for inferred length. Slice type built in, using [] suffix, e.g. int[] Distinct types, similar to a typedef but forms a new type. (Example: the String type is a distinct char[] ) Built-in 128-bit integer on all platforms. char is an unsigned 8-bit integer. ichar is its signed counterpart. Well-defined bitwidth for integer types: ichar/char (8 bits), short/ushort (16 bits), int/uint (32 bits), long/ulong (64 bits), int128/uint128 (128 bits) Pointer-sized iptr and uptr integers. isz and usz integers corresponding to the size_t bitwidth. Optional types are formed using the ! suffix. bool is the boolean type. typeid is a unique type identifier for a type, it can be used at runtime and compile time. any* contains a typeid and void* allowing it to act as a reference to any type of value. anyfault holds any fault value (see below). Changed Inferred array type uses [*] (e.g. int[*] x = { 1, 2 }; ). Flexible array member uses [*] . Removed The spiral rule type declaration (see above). Complex types size_t, ptrdiff_t (see above). Array types do not decay. Types Added bitstruct a struct with a container type allowing precise control over bit-layout, replacing bitfields and enum masks. fault an enum type with unique values which are used together with optional. Vector types. Optional types. enum allows a set of unique constants to be associated with each enum value. Compile time reflection and limited runtime reflection on types (see \"Reflection\") All types have a typeid property uniquely referring to that particular type. Distinct types, which are similar to aliases, but represent distinctly different types. Types may have methods. Methods can be added to any type, including built-in types. Subtyping: using inline on a struct member allows a struct to be implicitly converted to this member type and use corresponding methods. Using inline on a distinct type allows it to be implicitly converted to its base type (but not vice versa). Types may add operator overloading to support foreach and subscript operations. Generic types through generic modules, using (< ... >) for the generic parameter list (e.g. List() list; ). Interface types, any* types which allows dynamic invocation of methods. Changed typedef is replaced by def and has somewhat different syntax (e.g. def MyTypeAlias = int; ). Function pointer syntax is prefix fn followed by a regular function declaration without the function name. Removed Enums, structs and unions no longer have distinct namespaces. Enum, struct and union declarations should not have a trailing ';' Inline typedef is not allowed. def can only be used at the top level. Anonymous structs are not allowed. Type qualifiers are all removed, including const , restrict , volatile Function pointers types cannot be used \"raw\", but must always be used through a type alias. Introspection Compile time type methods: alignof , associated , elements , extnameof , inf , inner , kindof , len , max , membersof , min , nan , names , params , returns , sizeof , typeid , values , qnameof , is_eq , is_ordered . Runtime type methods: inner , kind , len , names , sizeof . Expressions Added Expression block using {| ... |} . Somewhat similar to GCC statement expressions. Array initializers may use ranges. (e.g. int[256] x = { [0..128] = 1 } ) ?: operator, returning the first value if it can be converted to a boolean true, otherwise the second value is returned. Orelse ?? returning the first value if it is a result, the second if the first value was an optional value. Rethrow ! suffix operator with an implicit return the value if it was an optional value. Dynamic calls, allowing calls to be made on the any* and interfaces dispatched using a dynamic mechanism. Create a slice using a range subscript (e.g. a[4..8] to form a slice from element 4 to element 8). Two range subscript methods: [start..inclusive_end] and [start:length] . Start, end and length may be omitted for default values. Indexing from end: slices, arrays and vectors may be indexed from the end using ^ . ^1 represents the last element. This works for ranges as well. Range assignment, assign a single value to an entire range e.g. a[4..8] = 1; . Slice assignment, copy one range to the other range e.g. a[4..8] = b[8..12]; . Array, vector and slice comparison: == can be used to make an element-wise comparison of two containers. ? suffix operator turns a fault into an optional value. !! suffix panics if the value is an optional value. $defined(...) returns true if the last expression is defined (sub-expressions must be valid). $and(...) $or(...) perform compile time && and || without semantically checking any elements after the first false/true respectively. Lambdas (anonymous functions) may be defined, they work just like functions and do not capture any state. Simple bitstructs (only containing booleans) may be manipulated using bit operations & ^ | ~ and assignment. Structs may implicitly convert to their inline member if they have one. Pointers to arrays may implicitly convert to slices. Any pointer may implicitly convert to an any* with type being the pointee. Optional values will implicitly invoke \"flatmap\" on an expression it is a subexpression of. Swizzling for arrays and vectors. Changed Compound literals use Type { ... } rather than (Type) { ... } Operator precedence of bit operations is higher than + and - . Well defined-evaluation order: left-to-right, assignment after expression evaluation. sizeof is $sizeof and only works on expressions. Use Type.sizeof on types. alignof is $alignof for expressions. Types use Type.alignof . Narrowing conversions are only allowed if all sub-expressions is as small or smaller than the type. Widening conversions are only allowed on simple expressions (i.e. most binary expressions and some unary may not be widened) Removed The comma operator is removed. Cast changes Functions Added Functions may be invoked using named arguments, the name is the dot-prefixed parameter name, e.g. foo(.name = a, .len = 2) . Typed varargs are declared Type... argument , and will take 0 or more arguments of the given type. It is possible to \"splat\" an array or slice into the location of a typed vararg using ... : foo(a, b, ...list) any* varargs are declared argument... , it can take 0 or more arguments of any type which are implicitly converted to the any* type. The function declaration may have @inline or @noinline as a default. Using @inline or @noinline on a function call expression will override the function default. Type methods are functions defined in the form fn void Foo.my_method(Foo* foo) { ... } , they can be invoked using dot syntax. Type methods may be attached to any type, even arrays and vectors. Error handling using optional return types. Changed Function declarations use the fn prefix. Removed Functions with C-style varargs may be called, and declared as external functions, but not used for C3 functions. Attributes C3 adds a long range of attributes in the form @name(...) . It is possible to create custom attribute groups using def (e.g. def MyAttribute(usz align) = { @aligned(align) @weak }; ) which groups certain attributes. Empty attribute groups are permitted. The complete list: @align , @benchmark , @bigendian , @builtin , @callconv , @deprecated , @dynamic , @export , @extern , @if , @inline , @interface , @littleendian , @local , @maydiscard , @naked , @nodiscard , @noinit , @noreturn , @nostrip , @obfuscate , @operator , @overlap , @priority , @private , @public , @pure , @reflect , @section , @test , @used , @unused . Declarations Added var declaration for type inferred variables in macros. E.g. var a = some_value; var declaration for new type variables in macros. E.g. var $Type = int; var declaration for compile time mutable variables in function and macros. E.g. var $foo = 1; const declarations may be untyped. Such constants are not stored in the resulting binary. Changed tlocal declares a variable to be thread local. static top level declarations are replaced with @local . ( static in functions is unchanged) Removed restrict removed. atomic should be replaced by atomic load/store operations. volatile should be replaced by volatile load/store operations. Statements Added Match-style variant of the switch statement, allows each case to hold an expression to test. Switching over type with typeid . Unpack any* to the underlying type with an any -switch. nextcase to fallthrough to the next case. nextcase to jump to the case with the expression value (this may be an expression evaluated at runtime). nextcase default to jump to the default clause. Labelled while / do / for / foreach to use with break nextcase and continue . foreach to iterate over arrays, vectors, slices and user-defined containers using operator overloading. foreach_r to iterate in reverse. foreach / foreach_r may take the element by value or reference. The index may optionally be provided. $if , $switch , $for , $foreach statements executing at compile time. $echo printing a message at compile time. $assert compile time assert. defer statement to execute statements at scope exit. defer catch and defer try similar to defer but executes only on optional exit or regular exit of scope respectively. do statements may omit while , behaving same as while (0) if may have a label. Labelled if may be exited using labelled break. asm blocks for inline assembly. if-try statements allows you to run code where an expression is a result. if-catch statements runs code on fault. It can be used to implicitly unwrap variables. Exhaustive switching on enums. Changed Switch cases will have implicit break, rather than implicit fallthrough. assert is an actual statement and may take a string or a format + arguments. static_assert is $assert and is a statement. Removed goto removed, replaced by labelled break, continue and nextcase. Compile time evaluation Added @if(cond) to conditionally include a struct/union field, a user-defined type etc. Compile time variables with $ prefix e.g. $foo . $if...$else...$endif and $switch...$endswitch inside of functions to conditionally include code. $for and $foreach to loop over compile time variables and data. $typeof determines an expression type without evaluating it. Type properties may be accessed at compile time. $checks determines if something may type check. $define returns true if the variable, function or type exists. $error emits an error if encountered. $embed includes a file as binary data. $include includes a file as text. $exec includes the output of a program as code. $evaltype takes a compile time string and turns it into a type. $eval takes a string and turns it into an identifier. $extnameof turns an identifier into its string external name. $nameof turns an identifier into its local string name. $qnameof turns an identifier into its local string name with the module prefixed. Compile time constant values are always compile time folded for arithmetic operations and casts. $$FUNCTION returns the current function as an identifier. Changed #define for constants is replaced by untyped constants, e.g. #define SOME_CONSTANT 1 becomes const SOME_CONSTANT = 1; . #define for variable and function aliases is replaced by def , e.g. #define native_foo win32_foo becomes def native_foo = win32_foo; In-function #if...#else..#endif is replaced by $if , #if...#elif...#endif is replaced by $switch . For converting code into a string use $stringify . Macros for date, line etc are replaced by $$DATE , $$FILE , $$FILEPATH , $$FUNC , $$LINE , $$MODULE , $$TIME . Removed Top level #if...#endif does not have a counterpart. Use @if instead. No #include directives, $include will include text but isn't for the same use. Macros Added macro for defining macros. \"Function-like\" macros have no prefix and has only regular parameters or type parameters. \"At\"-macros are prefixed with @ and may also have compile time values, expression and ref parameters, and may have a trailing body. Type parameters have the prefix $ and conform to the type naming standard (\"$TypeFoo\"). \"ref\" parameters are declared using with a & prefix operator. This is similar to C++ ref parameters. Expression parameters are unevaluated expressions, this is similar to arguments to #define . Compile time values have a $ prefix and must contain compile time constant values. Any macro that evaluates to a constant result can be used as if it was the resulting constant. Macros may be recursively evaluated. Macros are inlined at the location where they are invoked. Unless resulting in a single constant, macros implicitly create a runtime scope. Removed No #define macros. Macros cannot be incomplete statements. Features provided by builtins Some features are provided by builtins, and appears as normal functions and macros in the standard library but nonetheless provided unique functionality: @likely(...) / @unlikely(...) on branches affects compilation optimization. @anycast(...) casts an any* with an optional result. unreachable(...) marks a path as unreachable with a panic in safe mode. unsupported(...) similar to unreachable but for functionality not implemented. @expect(...) expect a certain value with an optional probability for the optimizer. @prefetch(...) prefect a pointer. swizzle(...) swizzles a vector. @volatile_load(...) and @volatile_store(...) volatile load/store. @atomic_load(...) and @atomic_store(...) atomic load/store. compare_exchange(...) atomic compare exchange. Saturating add, sub, mul, shl on integers. Vector reduce operations: add, mul, and, or, xor, max, min. Modules Modules are defined using module . Where name is on the form foo::bar::baz Modules can be split into an unlimited number of module sections, each starting with the same module name declaration. The import statement imports a given module. Each module section has its own set of import statements. Importing a module gives access to the declarations that are @public . Declarations are default @public , but a module section may set a different default (e.g. module my_module @private; ) @private means the declaration is only visible in the module. @local means only visible to the current module section. Imports are recursive. For example, import my_lib will implicitly also import my_lib::net . Multiple imports may be specified with the same import , e.g. import std::net, std::io; . Generic modules have a set of parameters after the module name module arr(); Generic modules are not type checked until any of its types, functions or globals are instantiated. Contracts Doc comments (starting with /** ) are parsed. The first part, up until the first @ directive is ignored. The @param directive for pointer arguments may define usage constraints [in] [out] and [inout] . Pointer argument constraints may add a & prefix to indicate that they may not be null , e.g. [&inout] . Contracts may be attached to generic modules, functions and macros. @require directives are evaluated given the arguments provided. Failing them may be a compile time or runtime error. The @ensure directive is evaluated at exit - if the return is a result and not an optional. return can be used as a variable identifier inside of @ensure , and holds the return value. @return! optionally lists the errors used. This will be checked at compile time. @pure says that no writing to globals is allowed inside and only @pure functions may be called. Benchmarking Benchmarks are indicated by @benchmark . Marking a module section @benchmark makes all functions inside of it implicitly benchmarks. Benchmarks are usually not compiled. Benchmarks are instead only run by the compiler on request. Testing Tests are indicated by @test . Marking a module section @test makes all functions inside of it implicitly tests. Tests are usually not compiled. Tests are instead only run by the compiler on request. Safe / fast Compilation has two modes: \"safe\" and \"fast\". Safe will insert checks for out-of-bounds access, null-pointer deref, shifting by negative numbers, division by zero, violation of contracts and asserts. Fast will assume all of those checks can be assumed to always pass. This means that unexpected behaviour may result from violating those checks. It is recommended to develop in \"safe\" mode. If debug symbols are available, C3 will produce a stack trace in safe mode where an error occurs.","title":"Feature list"},{"location":"allfeatures/#feature-summary","text":"Here is a summary of all the features of C3 and changes from C","title":"Feature summary"},{"location":"allfeatures/#symbols-and-literals","text":"Changes relating to literals, identifiers etc.","title":"Symbols and literals"},{"location":"allfeatures/#added","text":"0o prefix for octal. 0b prefix for binary. Optional \"_\" as digit separator. Hexadecimal byte data, e.g x\"abcd\" . Base64 byte data, e.g. b64\"QzM=\" . Type name restrictions (PascalCase). Variable and function name restrictions (must start with lower case letter). Constant name restrictions (no lower case). Character literals may be 2, 4, 8, 16 bytes long. (2cc, 4cc etc). Raw string literals between \"`\". \\e escape character. Source code must be UTF-8. Assumes \\n for new row \\r is stripped from source. Bit-width integer and float suffixes: u8/i8/u16/i16/... f32/f64/... The null literal is a pointer value of 0. The true and false are boolean constants true and false.","title":"Added"},{"location":"allfeatures/#removed","text":"Trigraphs / digraphs. 0123-style octal. z , LL and ULL suffixes.","title":"Removed"},{"location":"allfeatures/#built-in-types","text":"","title":"Built-in types"},{"location":"allfeatures/#added_1","text":"Type declaration is left to right: int[4]*[2] a; instead of int (*a[2])[4]; Simd vector types using [<>] syntax, e.g. float[<4>] , use [<*>] for inferred length. Slice type built in, using [] suffix, e.g. int[] Distinct types, similar to a typedef but forms a new type. (Example: the String type is a distinct char[] ) Built-in 128-bit integer on all platforms. char is an unsigned 8-bit integer. ichar is its signed counterpart. Well-defined bitwidth for integer types: ichar/char (8 bits), short/ushort (16 bits), int/uint (32 bits), long/ulong (64 bits), int128/uint128 (128 bits) Pointer-sized iptr and uptr integers. isz and usz integers corresponding to the size_t bitwidth. Optional types are formed using the ! suffix. bool is the boolean type. typeid is a unique type identifier for a type, it can be used at runtime and compile time. any* contains a typeid and void* allowing it to act as a reference to any type of value. anyfault holds any fault value (see below).","title":"Added"},{"location":"allfeatures/#changed","text":"Inferred array type uses [*] (e.g. int[*] x = { 1, 2 }; ). Flexible array member uses [*] .","title":"Changed"},{"location":"allfeatures/#removed_1","text":"The spiral rule type declaration (see above). Complex types size_t, ptrdiff_t (see above). Array types do not decay.","title":"Removed"},{"location":"allfeatures/#types","text":"","title":"Types"},{"location":"allfeatures/#added_2","text":"bitstruct a struct with a container type allowing precise control over bit-layout, replacing bitfields and enum masks. fault an enum type with unique values which are used together with optional. Vector types. Optional types. enum allows a set of unique constants to be associated with each enum value. Compile time reflection and limited runtime reflection on types (see \"Reflection\") All types have a typeid property uniquely referring to that particular type. Distinct types, which are similar to aliases, but represent distinctly different types. Types may have methods. Methods can be added to any type, including built-in types. Subtyping: using inline on a struct member allows a struct to be implicitly converted to this member type and use corresponding methods. Using inline on a distinct type allows it to be implicitly converted to its base type (but not vice versa). Types may add operator overloading to support foreach and subscript operations. Generic types through generic modules, using (< ... >) for the generic parameter list (e.g. List() list; ). Interface types, any* types which allows dynamic invocation of methods.","title":"Added"},{"location":"allfeatures/#changed_1","text":"typedef is replaced by def and has somewhat different syntax (e.g. def MyTypeAlias = int; ). Function pointer syntax is prefix fn followed by a regular function declaration without the function name.","title":"Changed"},{"location":"allfeatures/#removed_2","text":"Enums, structs and unions no longer have distinct namespaces. Enum, struct and union declarations should not have a trailing ';' Inline typedef is not allowed. def can only be used at the top level. Anonymous structs are not allowed. Type qualifiers are all removed, including const , restrict , volatile Function pointers types cannot be used \"raw\", but must always be used through a type alias.","title":"Removed"},{"location":"allfeatures/#introspection","text":"Compile time type methods: alignof , associated , elements , extnameof , inf , inner , kindof , len , max , membersof , min , nan , names , params , returns , sizeof , typeid , values , qnameof , is_eq , is_ordered . Runtime type methods: inner , kind , len , names , sizeof .","title":"Introspection"},{"location":"allfeatures/#expressions","text":"","title":"Expressions"},{"location":"allfeatures/#added_3","text":"Expression block using {| ... |} . Somewhat similar to GCC statement expressions. Array initializers may use ranges. (e.g. int[256] x = { [0..128] = 1 } ) ?: operator, returning the first value if it can be converted to a boolean true, otherwise the second value is returned. Orelse ?? returning the first value if it is a result, the second if the first value was an optional value. Rethrow ! suffix operator with an implicit return the value if it was an optional value. Dynamic calls, allowing calls to be made on the any* and interfaces dispatched using a dynamic mechanism. Create a slice using a range subscript (e.g. a[4..8] to form a slice from element 4 to element 8). Two range subscript methods: [start..inclusive_end] and [start:length] . Start, end and length may be omitted for default values. Indexing from end: slices, arrays and vectors may be indexed from the end using ^ . ^1 represents the last element. This works for ranges as well. Range assignment, assign a single value to an entire range e.g. a[4..8] = 1; . Slice assignment, copy one range to the other range e.g. a[4..8] = b[8..12]; . Array, vector and slice comparison: == can be used to make an element-wise comparison of two containers. ? suffix operator turns a fault into an optional value. !! suffix panics if the value is an optional value. $defined(...) returns true if the last expression is defined (sub-expressions must be valid). $and(...) $or(...) perform compile time && and || without semantically checking any elements after the first false/true respectively. Lambdas (anonymous functions) may be defined, they work just like functions and do not capture any state. Simple bitstructs (only containing booleans) may be manipulated using bit operations & ^ | ~ and assignment. Structs may implicitly convert to their inline member if they have one. Pointers to arrays may implicitly convert to slices. Any pointer may implicitly convert to an any* with type being the pointee. Optional values will implicitly invoke \"flatmap\" on an expression it is a subexpression of. Swizzling for arrays and vectors.","title":"Added"},{"location":"allfeatures/#changed_2","text":"Compound literals use Type { ... } rather than (Type) { ... } Operator precedence of bit operations is higher than + and - . Well defined-evaluation order: left-to-right, assignment after expression evaluation. sizeof is $sizeof and only works on expressions. Use Type.sizeof on types. alignof is $alignof for expressions. Types use Type.alignof . Narrowing conversions are only allowed if all sub-expressions is as small or smaller than the type. Widening conversions are only allowed on simple expressions (i.e. most binary expressions and some unary may not be widened)","title":"Changed"},{"location":"allfeatures/#removed_3","text":"The comma operator is removed.","title":"Removed"},{"location":"allfeatures/#cast-changes","text":"","title":"Cast changes"},{"location":"allfeatures/#functions","text":"","title":"Functions"},{"location":"allfeatures/#added_4","text":"Functions may be invoked using named arguments, the name is the dot-prefixed parameter name, e.g. foo(.name = a, .len = 2) . Typed varargs are declared Type... argument , and will take 0 or more arguments of the given type. It is possible to \"splat\" an array or slice into the location of a typed vararg using ... : foo(a, b, ...list) any* varargs are declared argument... , it can take 0 or more arguments of any type which are implicitly converted to the any* type. The function declaration may have @inline or @noinline as a default. Using @inline or @noinline on a function call expression will override the function default. Type methods are functions defined in the form fn void Foo.my_method(Foo* foo) { ... } , they can be invoked using dot syntax. Type methods may be attached to any type, even arrays and vectors. Error handling using optional return types.","title":"Added"},{"location":"allfeatures/#changed_3","text":"Function declarations use the fn prefix.","title":"Changed"},{"location":"allfeatures/#removed_4","text":"Functions with C-style varargs may be called, and declared as external functions, but not used for C3 functions.","title":"Removed"},{"location":"allfeatures/#attributes","text":"C3 adds a long range of attributes in the form @name(...) . It is possible to create custom attribute groups using def (e.g. def MyAttribute(usz align) = { @aligned(align) @weak }; ) which groups certain attributes. Empty attribute groups are permitted. The complete list: @align , @benchmark , @bigendian , @builtin , @callconv , @deprecated , @dynamic , @export , @extern , @if , @inline , @interface , @littleendian , @local , @maydiscard , @naked , @nodiscard , @noinit , @noreturn , @nostrip , @obfuscate , @operator , @overlap , @priority , @private , @public , @pure , @reflect , @section , @test , @used , @unused .","title":"Attributes"},{"location":"allfeatures/#declarations","text":"","title":"Declarations"},{"location":"allfeatures/#added_5","text":"var declaration for type inferred variables in macros. E.g. var a = some_value; var declaration for new type variables in macros. E.g. var $Type = int; var declaration for compile time mutable variables in function and macros. E.g. var $foo = 1; const declarations may be untyped. Such constants are not stored in the resulting binary.","title":"Added"},{"location":"allfeatures/#changed_4","text":"tlocal declares a variable to be thread local. static top level declarations are replaced with @local . ( static in functions is unchanged)","title":"Changed"},{"location":"allfeatures/#removed_5","text":"restrict removed. atomic should be replaced by atomic load/store operations. volatile should be replaced by volatile load/store operations.","title":"Removed"},{"location":"allfeatures/#statements","text":"","title":"Statements"},{"location":"allfeatures/#added_6","text":"Match-style variant of the switch statement, allows each case to hold an expression to test. Switching over type with typeid . Unpack any* to the underlying type with an any -switch. nextcase to fallthrough to the next case. nextcase to jump to the case with the expression value (this may be an expression evaluated at runtime). nextcase default to jump to the default clause. Labelled while / do / for / foreach to use with break nextcase and continue . foreach to iterate over arrays, vectors, slices and user-defined containers using operator overloading. foreach_r to iterate in reverse. foreach / foreach_r may take the element by value or reference. The index may optionally be provided. $if , $switch , $for , $foreach statements executing at compile time. $echo printing a message at compile time. $assert compile time assert. defer statement to execute statements at scope exit. defer catch and defer try similar to defer but executes only on optional exit or regular exit of scope respectively. do statements may omit while , behaving same as while (0) if may have a label. Labelled if may be exited using labelled break. asm blocks for inline assembly. if-try statements allows you to run code where an expression is a result. if-catch statements runs code on fault. It can be used to implicitly unwrap variables. Exhaustive switching on enums.","title":"Added"},{"location":"allfeatures/#changed_5","text":"Switch cases will have implicit break, rather than implicit fallthrough. assert is an actual statement and may take a string or a format + arguments. static_assert is $assert and is a statement.","title":"Changed"},{"location":"allfeatures/#removed_6","text":"goto removed, replaced by labelled break, continue and nextcase.","title":"Removed"},{"location":"allfeatures/#compile-time-evaluation","text":"","title":"Compile time evaluation"},{"location":"allfeatures/#added_7","text":"@if(cond) to conditionally include a struct/union field, a user-defined type etc. Compile time variables with $ prefix e.g. $foo . $if...$else...$endif and $switch...$endswitch inside of functions to conditionally include code. $for and $foreach to loop over compile time variables and data. $typeof determines an expression type without evaluating it. Type properties may be accessed at compile time. $checks determines if something may type check. $define returns true if the variable, function or type exists. $error emits an error if encountered. $embed includes a file as binary data. $include includes a file as text. $exec includes the output of a program as code. $evaltype takes a compile time string and turns it into a type. $eval takes a string and turns it into an identifier. $extnameof turns an identifier into its string external name. $nameof turns an identifier into its local string name. $qnameof turns an identifier into its local string name with the module prefixed. Compile time constant values are always compile time folded for arithmetic operations and casts. $$FUNCTION returns the current function as an identifier.","title":"Added"},{"location":"allfeatures/#changed_6","text":"#define for constants is replaced by untyped constants, e.g. #define SOME_CONSTANT 1 becomes const SOME_CONSTANT = 1; . #define for variable and function aliases is replaced by def , e.g. #define native_foo win32_foo becomes def native_foo = win32_foo; In-function #if...#else..#endif is replaced by $if , #if...#elif...#endif is replaced by $switch . For converting code into a string use $stringify . Macros for date, line etc are replaced by $$DATE , $$FILE , $$FILEPATH , $$FUNC , $$LINE , $$MODULE , $$TIME .","title":"Changed"},{"location":"allfeatures/#removed_7","text":"Top level #if...#endif does not have a counterpart. Use @if instead. No #include directives, $include will include text but isn't for the same use.","title":"Removed"},{"location":"allfeatures/#macros","text":"","title":"Macros"},{"location":"allfeatures/#added_8","text":"macro for defining macros. \"Function-like\" macros have no prefix and has only regular parameters or type parameters. \"At\"-macros are prefixed with @ and may also have compile time values, expression and ref parameters, and may have a trailing body. Type parameters have the prefix $ and conform to the type naming standard (\"$TypeFoo\"). \"ref\" parameters are declared using with a & prefix operator. This is similar to C++ ref parameters. Expression parameters are unevaluated expressions, this is similar to arguments to #define . Compile time values have a $ prefix and must contain compile time constant values. Any macro that evaluates to a constant result can be used as if it was the resulting constant. Macros may be recursively evaluated. Macros are inlined at the location where they are invoked. Unless resulting in a single constant, macros implicitly create a runtime scope.","title":"Added"},{"location":"allfeatures/#removed_8","text":"No #define macros. Macros cannot be incomplete statements.","title":"Removed"},{"location":"allfeatures/#features-provided-by-builtins","text":"Some features are provided by builtins, and appears as normal functions and macros in the standard library but nonetheless provided unique functionality: @likely(...) / @unlikely(...) on branches affects compilation optimization. @anycast(...) casts an any* with an optional result. unreachable(...) marks a path as unreachable with a panic in safe mode. unsupported(...) similar to unreachable but for functionality not implemented. @expect(...) expect a certain value with an optional probability for the optimizer. @prefetch(...) prefect a pointer. swizzle(...) swizzles a vector. @volatile_load(...) and @volatile_store(...) volatile load/store. @atomic_load(...) and @atomic_store(...) atomic load/store. compare_exchange(...) atomic compare exchange. Saturating add, sub, mul, shl on integers. Vector reduce operations: add, mul, and, or, xor, max, min.","title":"Features provided by builtins"},{"location":"allfeatures/#modules","text":"Modules are defined using module . Where name is on the form foo::bar::baz Modules can be split into an unlimited number of module sections, each starting with the same module name declaration. The import statement imports a given module. Each module section has its own set of import statements. Importing a module gives access to the declarations that are @public . Declarations are default @public , but a module section may set a different default (e.g. module my_module @private; ) @private means the declaration is only visible in the module. @local means only visible to the current module section. Imports are recursive. For example, import my_lib will implicitly also import my_lib::net . Multiple imports may be specified with the same import , e.g. import std::net, std::io; . Generic modules have a set of parameters after the module name module arr(); Generic modules are not type checked until any of its types, functions or globals are instantiated.","title":"Modules"},{"location":"allfeatures/#contracts","text":"Doc comments (starting with /** ) are parsed. The first part, up until the first @ directive is ignored. The @param directive for pointer arguments may define usage constraints [in] [out] and [inout] . Pointer argument constraints may add a & prefix to indicate that they may not be null , e.g. [&inout] . Contracts may be attached to generic modules, functions and macros. @require directives are evaluated given the arguments provided. Failing them may be a compile time or runtime error. The @ensure directive is evaluated at exit - if the return is a result and not an optional. return can be used as a variable identifier inside of @ensure , and holds the return value. @return! optionally lists the errors used. This will be checked at compile time. @pure says that no writing to globals is allowed inside and only @pure functions may be called.","title":"Contracts"},{"location":"allfeatures/#benchmarking","text":"Benchmarks are indicated by @benchmark . Marking a module section @benchmark makes all functions inside of it implicitly benchmarks. Benchmarks are usually not compiled. Benchmarks are instead only run by the compiler on request.","title":"Benchmarking"},{"location":"allfeatures/#testing","text":"Tests are indicated by @test . Marking a module section @test makes all functions inside of it implicitly tests. Tests are usually not compiled. Tests are instead only run by the compiler on request.","title":"Testing"},{"location":"allfeatures/#safe-fast","text":"Compilation has two modes: \"safe\" and \"fast\". Safe will insert checks for out-of-bounds access, null-pointer deref, shifting by negative numbers, division by zero, violation of contracts and asserts. Fast will assume all of those checks can be assumed to always pass. This means that unexpected behaviour may result from violating those checks. It is recommended to develop in \"safe\" mode. If debug symbols are available, C3 will produce a stack trace in safe mode where an error occurs.","title":"Safe / fast"},{"location":"anyinterfaces/","text":"Any and interfaces 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.","title":"Any and interfaces"},{"location":"anyinterfaces/#any-and-interfaces","text":"","title":"Any and interfaces"},{"location":"anyinterfaces/#working-with-the-type-of-any-at-runtime","text":"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);","title":"Working with the type of any* at runtime."},{"location":"anyinterfaces/#variable-argument-functions-with-implicit-any","text":"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:","title":"Variable argument functions with implicit any"},{"location":"anyinterfaces/#explicit-any-vararg-functions","text":"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:","title":"Explicit any vararg functions"},{"location":"anyinterfaces/#implicit-any-vararg-functions","text":"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.","title":"Implicit any vararg functions"},{"location":"anyinterfaces/#interfaces","text":"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 .","title":"Interfaces"},{"location":"anyinterfaces/#defining-an-interface","text":"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.","title":"Defining an interface"},{"location":"anyinterfaces/#implementing-the-interface","text":"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); }","title":"Implementing the interface"},{"location":"anyinterfaces/#dynamic-methods","text":"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.","title":"\"@dynamic\" methods"},{"location":"anyinterfaces/#referring-to-an-interface-by-pointer","text":"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\"","title":"Referring to an interface by pointer"},{"location":"anyinterfaces/#calling-dynamic-methods","text":"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!\" }","title":"Calling dynamic methods"},{"location":"anyinterfaces/#reflection-invocation","text":"This functionality is not yet implemented and may see syntax changes It is possible to retrieve any @dynamic function by name and invoke it: def VoidMethodFn = fn void(void*); fn void* int.test_something(&self) @dynamic { io::printfn(\"Testing: %d\", *self); } fn void main() { int z = 321; any* a = &z; VoidMethodFn test_func = a.reflect(\"test_something\"); test_func(a); // Will print \"Testing: 321\" } This feature allows methods to be linked up at runtime.","title":"Reflection invocation"},{"location":"arrays/","text":"Arrays 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","title":"Arrays"},{"location":"arrays/#arrays","text":"Arrays has a central role in programming. C3 offers 2 built-in types of arrays:","title":"Arrays"},{"location":"arrays/#fixed-arrays","text":"[] 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]","title":"Fixed arrays"},{"location":"arrays/#subarray","text":"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.","title":"Subarray"},{"location":"arrays/#slicing-arrays","text":"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.","title":"Slicing arrays"},{"location":"arrays/#conversion-list","text":"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];","title":"Conversion list"},{"location":"arrays/#internals","text":"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; }","title":"Internals"},{"location":"arrays/#iteration-over-arrays","text":"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","title":"Iteration over arrays"},{"location":"asm/","text":"Inline assembly C3 provides two ways to insert inline assembly: asm strings and asm blocks. Asm strings This form takes a single compile time string and passes it directly to the underlying backend without any changes. int x = 0; asm(\"nop\"); int y = x; Asm block Asm blocks uses a common grammar for all types of processors. It assumes that all assembly statements can be reduced to the format: instruction (arg (',' arg)*)?; Where an arg is: An identifier, e.g. FOO , x . A numeric constant 1 0xFF etc. A register name (always lower case with a '$' prefix) e.g. $eax $r7 . The address of a variable e.g. &x . An indirect address: [addr] or [addr + index * + offset] . Any expression inside of \"()\" (will be evaluated before entering the asm block). An example: int aa = 3; int g; int* gp = &g; int* xa = &a; usz asf = 1; asm { movl x, 4; // Move 4 into the variable x movl [gp], x; // Move the value of x into the address in gp movl x, 1; // Move 1 into x movl [xa + asf * 4 + 4], x; // Move x into the address at xa[asf + 1] movl $eax, (23 + x); // Move 23 + x into EAX movl x, $eax; // Move EAX into x movq [&z], 33; // Move 33 into the memory address of z } The asm block will infer register clobbers and in/out parameters. *Please note that the current state of inline asm is a work in progress , only a subset of x86 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.","title":"Inline asm"},{"location":"asm/#inline-assembly","text":"C3 provides two ways to insert inline assembly: asm strings and asm blocks.","title":"Inline assembly"},{"location":"asm/#asm-strings","text":"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;","title":"Asm strings"},{"location":"asm/#asm-block","text":"Asm blocks uses a common grammar for all types of processors. It assumes that all assembly statements can be reduced to the format: instruction (arg (',' arg)*)?; Where an arg is: An identifier, e.g. FOO , x . A numeric constant 1 0xFF etc. A register name (always lower case with a '$' prefix) e.g. $eax $r7 . The address of a variable e.g. &x . An indirect address: [addr] or [addr + index * + offset] . Any expression inside of \"()\" (will be evaluated before entering the asm block). An example: int aa = 3; int g; int* gp = &g; int* xa = &a; usz asf = 1; asm { movl x, 4; // Move 4 into the variable x movl [gp], x; // Move the value of x into the address in gp movl x, 1; // Move 1 into x movl [xa + asf * 4 + 4], x; // Move x into the address at xa[asf + 1] movl $eax, (23 + x); // Move 23 + x into EAX movl x, $eax; // Move EAX into x movq [&z], 33; // Move 33 into the memory address of z } The asm block will infer register clobbers and in/out parameters. *Please note that the current state of inline asm is a work in progress , only a subset of x86 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.","title":"Asm block"},{"location":"attributes/","text":"Attributes 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 = {};","title":"Attributes"},{"location":"attributes/#attributes","text":"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.","title":"Attributes"},{"location":"attributes/#built-in-attributes","text":"","title":"Built in attributes"},{"location":"attributes/#alignalignment-struct-bitstructs-union-var-fn","text":"This attribute sets the minimum alignment for a field or a variable.","title":"@align(alignment) (struct, bitstructs, union, var, fn)"},{"location":"attributes/#benchmark-fn","text":"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.","title":"@benchmark (fn)"},{"location":"attributes/#bigendian-bitstruct","text":"Lays out the bits as if the data was stored in a big endian type, regardless of host system endianness.","title":"@bigendian (bitstruct)"},{"location":"attributes/#builtin-fn-macro-global-const","text":"Allows a macro, function, global or constant be used from another module without the module path prefixed. Should be used sparingly.","title":"@builtin (fn, macro, global, const)"},{"location":"attributes/#callc-fn","text":"Sets the call convention, which may be ignored if the convention is not supported on the target. Valid arguments are veccall , ccall , stdcall .","title":"@callc (fn)"},{"location":"attributes/#deprecated-types-fn-macro-global-const-member","text":"Marks the particular type, global, const or member as deprecated, making use trigger a warning.","title":"@deprecated (types, fn, macro, global, const, member)"},{"location":"attributes/#export-fn-global-const-enum-union-struct-fault","text":"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.","title":"@export (fn, global, const, enum, union, struct, fault)"},{"location":"attributes/#extern-fn-global-const-enum-union-struct-fault","text":"Sets the external (linkage) name of this declaration.","title":"@extern (fn, global, const, enum, union, struct, fault)"},{"location":"attributes/#finalizer-function","text":"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.","title":"@finalizer (function)"},{"location":"attributes/#if-all-declarations","text":"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.","title":"@if (all declarations)"},{"location":"attributes/#init-function","text":"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.","title":"@init (function)"},{"location":"attributes/#inline-fn-call","text":"Declares a function to always be inlined or if placed on a call, that the call should be inlined.","title":"@inline (fn, call)"},{"location":"attributes/#littleendian-bitstruct","text":"Lays out the bits as if the data was stored in a little endian type, regardless of host system endianness.","title":"@littleendian (bitstruct)"},{"location":"attributes/#local-any-declaration","text":"Sets the visibility to \"local\", which means it's only visible in the current module section.","title":"@local (any declaration)"},{"location":"attributes/#maydiscard-fn-macro","text":"Allows the return value of the function or macro to be discarded even if it is an optional. Should be used sparingly.","title":"@maydiscard (fn, macro)"},{"location":"attributes/#naked-fn","text":"This attribute disables prologue / epilogue emission for the function.","title":"@naked (fn)"},{"location":"attributes/#nodiscard-fn-macro","text":"The return value may not be discarded.","title":"@nodiscard (fn, macro)"},{"location":"attributes/#noinit-global-local","text":"Prevents the compiler from zero initializing the variable.","title":"@noinit (global, local)"},{"location":"attributes/#noreturn-fn","text":"Declares that the function will never return.","title":"@noreturn (fn)"},{"location":"attributes/#nostrip-any-declaration","text":"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.","title":"@nostrip (any declaration)"},{"location":"attributes/#obfuscate-any-declaration","text":"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.","title":"@obfuscate (any declaration)"},{"location":"attributes/#operator-method-macro-method","text":"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.","title":"@operator (method, macro method)"},{"location":"attributes/#overlap-bitstruct","text":"Allows bitstruct fields to have overlapping bit ranges.","title":"@overlap (bitstruct)"},{"location":"attributes/#packed-struct-union","text":"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 .","title":"@packed (struct, union)"},{"location":"attributes/#private-any-declaration","text":"Sets the visibility to \"private\", which means it is visible in the same module, but not from other modules.","title":"@private (any declaration)"},{"location":"attributes/#pure-call","text":"Used to annotate a non pure function as \"pure\" when checking for conformance to @pure on functions.","title":"@pure (call)"},{"location":"attributes/#packed-struct-union-enum","text":"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.","title":"@packed (struct, union, enum)"},{"location":"attributes/#reflect-any-declaration","text":"Adds additional reflection information. Has no effect currently.","title":"@reflect (any declaration)"},{"location":"attributes/#sectionname-fn-const-global","text":"Declares that a global variable or function should appear in a specific section.","title":"@section(name) (fn, const, global)"},{"location":"attributes/#test-fn","text":"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.","title":"@test (fn)"},{"location":"attributes/#unused-any-declaration","text":"Marks the declaration as possibly unused (but should not emit a warning).","title":"@unused (any declaration)"},{"location":"attributes/#used-any-declaration","text":"Marks a parameter, value etc. as must being used.","title":"@used (any declaration)"},{"location":"attributes/#weak-fn-const-global","text":"Emits a weak symbol rather than a global.","title":"@weak (fn, const, global)"},{"location":"attributes/#user-defined-attributes","text":"User defined attributes are intended for conditional application of built-in attributes. def @MyAttribute = { @noreturn @inline }; // The following two are equivalent: fn void foo() @MyAttribute { ... } fn void foo() @noreturn @inline { ... } A user defined attribute may also be completely empty: def @MyAttributeEmpty = {};","title":"User defined attributes"},{"location":"buildcommands/","text":"Build Commands When starting out, with C3 it's natural to use run to try things out. For larger projects, the built-in build system is instead recommended. By default the compiler is compiling stand-alone files to output an executable binary. c3c run The run command works same as compilation, but also immediately runs the resulting executable. c3c run Common additional parameters Additional parameters: - --lib add a library to search. - --output override the output directory. - --path execute as if standing at init c3c init [optional path] . Create a new project structure in the current directory. Use the --template to select a template. The following are built in: default - the default template, produces an executable. lib - template for producing a library. staticlib - template for producing a static library. It is also possible to give the path to a custom template. Additional parameters: - --template indicate an alternative template to use. c3c init hello_world will create the following structure: $ tree . . \u2514\u2500\u2500 hello_world \u251c\u2500\u2500 LICENSE \u251c\u2500\u2500 README.md \u251c\u2500\u2500 build \u251c\u2500\u2500 docs \u2502 \u251c\u2500\u2500 about.md \u2502 \u2514\u2500\u2500 src \u2502 \u2514\u2500\u2500 index.html \u251c\u2500\u2500 lib \u251c\u2500\u2500 project.c3p \u251c\u2500\u2500 resources \u251c\u2500\u2500 src \u2502 \u2514\u2500\u2500 hello_world \u2502 \u2514\u2500\u2500 main.c3 \u2514\u2500\u2500 test \u2514\u2500\u2500 hello_world build build [target] Build the project in the current path. It doesn't matter where in the project structure you are. The built-in templates define two targets: debug (which is the default) and release . clean clean build-run build-run [target] Build the target (if needed) and run the executable. clean-run clean-run [target] Clean, build and run the target. dist dist [target] Clean, build and package the target. docs docs [target] Rebuilds the documentation. bench bench [target] Runs benchmarks on a target.","title":"Commands"},{"location":"buildcommands/#build-commands","text":"When starting out, with C3 it's natural to use run to try things out. For larger projects, the built-in build system is instead recommended. By default the compiler is compiling stand-alone files to output an executable binary. c3c ","title":"Build Commands"},{"location":"buildcommands/#run","text":"The run command works same as compilation, but also immediately runs the resulting executable. c3c run ","title":"run"},{"location":"buildcommands/#common-additional-parameters","text":"Additional parameters: - --lib add a library to search. - --output override the output directory. - --path execute as if standing at","title":"Common additional parameters"},{"location":"buildcommands/#init","text":"c3c init [optional path] . Create a new project structure in the current directory. Use the --template to select a template. The following are built in: default - the default template, produces an executable. lib - template for producing a library. staticlib - template for producing a static library. It is also possible to give the path to a custom template. Additional parameters: - --template indicate an alternative template to use. c3c init hello_world will create the following structure: $ tree . . \u2514\u2500\u2500 hello_world \u251c\u2500\u2500 LICENSE \u251c\u2500\u2500 README.md \u251c\u2500\u2500 build \u251c\u2500\u2500 docs \u2502 \u251c\u2500\u2500 about.md \u2502 \u2514\u2500\u2500 src \u2502 \u2514\u2500\u2500 index.html \u251c\u2500\u2500 lib \u251c\u2500\u2500 project.c3p \u251c\u2500\u2500 resources \u251c\u2500\u2500 src \u2502 \u2514\u2500\u2500 hello_world \u2502 \u2514\u2500\u2500 main.c3 \u2514\u2500\u2500 test \u2514\u2500\u2500 hello_world","title":"init"},{"location":"buildcommands/#build","text":"build [target] Build the project in the current path. It doesn't matter where in the project structure you are. The built-in templates define two targets: debug (which is the default) and release .","title":"build"},{"location":"buildcommands/#clean","text":"clean","title":"clean"},{"location":"buildcommands/#build-run","text":"build-run [target] Build the target (if needed) and run the executable.","title":"build-run"},{"location":"buildcommands/#clean-run","text":"clean-run [target] Clean, build and run the target.","title":"clean-run"},{"location":"buildcommands/#dist","text":"dist [target] Clean, build and package the target.","title":"dist"},{"location":"buildcommands/#docs","text":"docs [target] Rebuilds the documentation.","title":"docs"},{"location":"buildcommands/#bench","text":"bench [target] Runs benchmarks on a target.","title":"bench"},{"location":"buildintro/","text":"Build System Building a project is done by invoking the C3 compiler with the build or run command inside of the project structure. The compiler will search upwards in the file hierarchy until a project.json file is found.","title":"Introduction"},{"location":"buildintro/#build-system","text":"Building a project is done by invoking the C3 compiler with the build or run command inside of the project structure. The compiler will search upwards in the file hierarchy until a project.json file is found.","title":"Build System"},{"location":"builtins/","text":"Builtins 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.","title":"Builtins"},{"location":"builtins/#builtins","text":"The compiler offers builtin constants and functions. Some are only available on certain targets. All builtins use the $$ name prefix.","title":"Builtins"},{"location":"builtins/#builtin-constants","text":"These can all safely be used by the user.","title":"Builtin constants"},{"location":"builtins/#benchmark_names","text":"An array of names of the benchmark functions.","title":"$$BENCHMARK_NAMES"},{"location":"builtins/#benchmark_fns","text":"An array of addresses to the benchmark functions.","title":"$$BENCHMARK_FNS"},{"location":"builtins/#date","text":"The current date.","title":"$$DATE"},{"location":"builtins/#file","text":"The current file name.","title":"$$FILE"},{"location":"builtins/#filepath","text":"The current file with path.","title":"$$FILEPATH"},{"location":"builtins/#func","text":"The current function name, will return \" \" on the global level.","title":"$$FUNC"},{"location":"builtins/#function","text":"The current function as an expression.","title":"$$FUNCTION"},{"location":"builtins/#line","text":"The current line as an integer.","title":"$$LINE"},{"location":"builtins/#line_raw","text":"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.","title":"$$LINE_RAW"},{"location":"builtins/#module","text":"The current module name.","title":"$$MODULE"},{"location":"builtins/#time","text":"The current time.","title":"$$TIME"},{"location":"builtins/#builtin-functions","text":"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.","title":"Builtin functions"},{"location":"builtins/#trap","text":"Emits a trap instruction.","title":"$$trap"},{"location":"builtins/#unreachable","text":"Inserts an \"unreachable\" annotation.","title":"$$unreachable"},{"location":"builtins/#stacktrace","text":"Returns the current \"callstack\" reference if available. Compiler dependent.","title":"$$stacktrace"},{"location":"builtins/#volatile_store","text":"Takes a variable and a value and stores the value as a volatile store.","title":"$$volatile_store"},{"location":"builtins/#volatile_load","text":"Takes a variable and returns the value using a volatile load.","title":"$$volatile_load"},{"location":"builtins/#memcpy","text":"Builtin memcpy instruction.","title":"$$memcpy"},{"location":"builtins/#memset","text":"Builtin memset instruction.","title":"$$memset"},{"location":"builtins/#prefetch","text":"Prefetch a memory location.","title":"$$prefetch"},{"location":"builtins/#sysclock","text":"Access to the cycle counter register (or similar low latency clock) on supported architectures (e.g. RDTSC on x86), otherwise $$sysclock will yield 0.","title":"$$sysclock"},{"location":"builtins/#syscall","text":"Makes a syscall according to the platform convention on platforms where it is supported.","title":"$$syscall"},{"location":"builtins/#math-functions","text":"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 .","title":"Math functions"},{"location":"builtins/#bit-functions","text":"","title":"Bit functions"},{"location":"builtins/#fshl-and-fshr","text":"Funnel shift left and right, takes either two integers or two integer vectors.","title":"$$fshl and $$fshr"},{"location":"builtins/#ctz-clz-bitreverse-bswap-popcount","text":"Bit functions work on an integer or an integer vector.","title":"$$ctz, $$clz, $$bitreverse, $$bswap, $$popcount"},{"location":"builtins/#vector-functions","text":"$$reduce_add , $$reduce_mul , $$reduce_and , $$reduce_or , $$reduce_xor work on integer vectors. $$reduce_fadd , $$reduce_fmul works on float vectors. $$reduce_max , $$reduce_min works on any vector. $$reverse reverses the values in any vector. $$shufflevector rearranges the values of two vectors using a fixed mask into a resulting vector.","title":"Vector functions"},{"location":"changes/","text":"Change log Revision 2023-10-24 Removal of $checks and @checked Additional chapter on vectors. Addition of $and / $or . Added is_eq and is_ordered type properties. Revision 2023-10-20 protocol => interface Overall update to reflection docs. Updates to distinct. Revision 2023-10-05 protocol Update to any* Revision 2023-10-03 exec and $exec change of static initializer Revision 2023-09-09 Removal of implicit void! -> anyfault conversion. Change @catchof to @catch . Revision 2023-07-22 Feature list. Macro ref parameter update. Abbreviated method first arg. Revision 2023-07-09 Update with $embed information. Revision 2023-07-07 Updated generics syntax and uses. Moved all \"ideas\" to the issue tracker. Revision 2023-06-23 Distinct inline explanation. Module sections. Revision 2023-06-11 Updated list of attributes. Better @if examples. Revision 2023-06-10 Updated with @if directive. Revision 2023-06-03 Added specific compiletime page. Updated examples with references. variant -> any, Revision 2023-06-05 Chapter on dynamic code. Updated some pages with references to dynamic calls. Revision 2023-06-02 Suffix '?' replaced by '!' and vice versa. Updates to ct statements. Fixes to array descriptions Replace try? / catch? with macros Updates to reflection define/typedef is now \"def\" Revision 2023-03-18 try? / catch? println -> printn Revision 2023-02-15 Added uint128 / int128 to the documentation. @private, @public and @local Revision 2023-02-14 Updated typedef / define Revision 2023-02-11 Changed @extname to @extern following compiler changes. Document @export Revision 2023-01-24 Consistently use printfn rather than printfln Added short function syntax. Added lambdas . Revision 2023-01-07 Direct download links added. Prefer \"String\" over char[] Revision 2022-12-28 Bitstruct documented. Revision 2022-11-18 Documented inline asm. Revision 2022-11-16 Move description of static initialize. Info on operator overload. More details on function pointers. Updated project tree. Revision 2022-11-07 Added information on enum associated values. Revision 2022-10-31 Updated project.json description. Added information about static initializers and finalizers. Revision 2022-10-14 libc::printf replaced with io::printf Revision 2022-10-01 Expanded and updated types . Revision 2022-07-20 Added start + len syntax Revision 2022-07-15 Added \"builtins\" Revision 2022-06-30 Updates to module system Revision 2022-04-05 Removal of @opaque . Revision 2022-03-26 Remove escape macros. Adding stringify and first class names. Removing \"?? jump\" Revision 2022-03-15 Removal of multi-line strings Revision 2022-03-04 Updates to $sizeof. Addition of $eval / $evaltype. Removal of $unreachable. Revision 2022-02-16 Updates to imports. Updates to project files. Revision 2022-02-09 Major revision to bring everything up to date. Revision 2021-10-20 func replaced by fn Compound literal now Type { ... } like C++. Update of conversion rules New error syntax Revision 2021-08-27 Updated reflection functionality. Added documentation for multi-line strings. Added documentation for base64 and hex array literals. Revision 2021-08-12 Updated error type and error handling with try/catch Revision 2021-07-13 Added nesting to / ... / removed /+ ... +/ Added primer. Revision 2021-06-20 Updated array layout. Revised macros for foreach. Removed old generic functions. Added new ideas around generic, macros Changed macro body definition syntax. Introduced both $for and $foreach. Revision 2021-05-31 Removal of vararray type. Updated user defined attributes. Removed incremental arrays. Added information on define . Added private modules and import. Revision 2021-05-18 Change cast to (type)(expression) Revision 2021-05-08 Added rationale for some changes from C. Updated undefined and undefined behaviour . Removed many of the fine-grained module features. Removed \"local\" visibility in modules . All modules are now distinct, parent modules do not have any special access to submodules. Added as module imports. Revision 2021-04-05 \"next\" is now \"nextcase\". Added link to the C3 discord. The conversions page updated with new conversion rules. Updated compound literal syntax. Removed undefined behaviour behaviour on integer overflow and added a list of unspecified behaviour. Revision 2020-12-23 Updated slice behaviour. Updated expression block syntax. Added link to specification-in-progress. Revision 2020-12-04 Local variables are implicitly zero. Removed in-block declarations. Changed struct member initialization syntax. Changed named parameter syntax. Updated on macro syntax. Removed built in c types. Revision 2020-08-22 Added slice operations. Changed cast syntax to cast( as ) . Revision 2020-07-08 Additions to error handling . Introduction of labelled nextcase , break and continue . Removal of goto . Revision 2020-06-17 Alternate casts in idea . Method functions simply renamed to \"method\". Completely revised error handling . Revision 2020-04-23 Updated error handling, adding try-else-jump and changed how errors are passed. Included reflection page Revision 2020-03-30 Added Odin and D to comparisons. Updated text on how to contribute. Updated the example on undefined behaviour. Updated text on conversions. Moved double -> float conversion to \"ideas\" Fixed some typos. Revision 2020-03-29 Type inference for enums. Included macro page. Corrected precedence rules with try and @ . Type functions. Managed variables back to ideas. Volatile moved back to ideas. Removed implicit lossy signed conversions. Introducing safe signed-unsigned comparisons. \"Function block\" renamed \"expression block\". @ sigil removed from macros and is only used with macro invocations. Changed cast syntax from @cast(Type, var) to cast(var, Type) Revision 2019-12-26 Added module versioning system idea . Fleshed out polymorphic functions. Unsigned to signed promotion mentioned in \"changes from C\" Revision 2019-12-25 Changes how generic modules work. Switched so that vararrays use Type[*] and sub arrays use Type[] . Added submodule granularity, partial imports (only importing selected functions and types), removal of local , extended aliasing. See modules . Updated \"changes from C\" with removal of multiple declarations. Revision 2019-12-11 Updated the setup page. Revision 2019-12-03 Added page on conversions . Added page on undefined behaviour . Revision 2019-11-01 Updated \"changes from C\" with the lack of array decays. Added FourCC to the language Added name alias to ideas Added align asserts to ideas Added built in tests to ideas Added arrays page Added function blocks to statements page . Added expressions page . Added variables page . Moved managed pointers from idea to the variables page . Revision 2019-09-30 Removed references (non-nullable pointers) Removed idea with aliasing in import Revision 2019-08-14 Compile time run-include and include ideas. New module system idea. Revision 2019-08-14 Namespace separator changed to :: instead of . to simplify parsing. Added FourCC, Macro text interpolation to ideas. Added Yacc grammar (incomplete) Added \"attribute\" keyword. Changed type alias declaration to use typedef ... as ... . Introduced type operator. Added section about attributes. Revision 2019-08-02 Added error example. Added generics example. Added method function example. Added idea implicit method functions Expanded the types page somewhat. Revision 2019-07-30 Added default and named arguments to the functions page . Added varargs to the functions page . Added idea about hierarchal memory. Added idea of raw dynamic safe arrays & strings. Volatile sections are no longer prefixed by '@' Added idea regarding c3 interop Added page about c interop . Removed c_ichar and c_uchar types as they are redundant. Updates to keywords on the grammar page ../syntax). Revision 2019-07-27 Updated grammar with keywords. Added the docs & comments page. Updated the pre- and post-conditions . Revision 2019-07-24 Idea: typed varargs. Added \"pure\" post condition Updated c3c commands. Removed the type keyword for defining union/struct/enum/error. Revision 2019-07-23 Added to generic functions examples for [] and []= Developed ideas about vectors in the idea section . Defined 2's complement for signed integers. Idea: Managed pointers. Updated naming rules for types. Added more naming rules + examples of them. Removed \"defer on function signatures\" from ideas. Removed \"managed qualifier\" from ideas. Removed \"defer sugar\" from ideas. Removed \"built in dynamic arrays\" from ideas. Added standard_library section. Added more about pre- and post-conditions . Revision 2019-07-22 Added \"Design Principles\" to the index page. Revision 2019-07-21 \"return\" rather than function name is used in post conditions. See Functions Added \"@include\" macro for textual includes. See Modules . Files to without module for single file compilations is now ok as a special case. See Modules Added cone style array idea to the idea section . Added idea about defer on error to the idea section . Added idea for aliasing generic structs in the import to the idea section . Added idea for changing automatic signed <-> unsigned conversion to the idea section . Added Changes from C and Statements sections. Removal of volatile . See Changes from C and Statements Removal of const See Changes from C","title":"Changes"},{"location":"changes/#change-log","text":"","title":"Change log"},{"location":"changes/#revision-2023-10-24","text":"Removal of $checks and @checked Additional chapter on vectors. Addition of $and / $or . Added is_eq and is_ordered type properties.","title":"Revision 2023-10-24"},{"location":"changes/#revision-2023-10-20","text":"protocol => interface Overall update to reflection docs. Updates to distinct.","title":"Revision 2023-10-20"},{"location":"changes/#revision-2023-10-05","text":"protocol Update to any*","title":"Revision 2023-10-05"},{"location":"changes/#revision-2023-10-03","text":"exec and $exec change of static initializer","title":"Revision 2023-10-03"},{"location":"changes/#revision-2023-09-09","text":"Removal of implicit void! -> anyfault conversion. Change @catchof to @catch .","title":"Revision 2023-09-09"},{"location":"changes/#revision-2023-07-22","text":"Feature list. Macro ref parameter update. Abbreviated method first arg.","title":"Revision 2023-07-22"},{"location":"changes/#revision-2023-07-09","text":"Update with $embed information.","title":"Revision 2023-07-09"},{"location":"changes/#revision-2023-07-07","text":"Updated generics syntax and uses. Moved all \"ideas\" to the issue tracker.","title":"Revision 2023-07-07"},{"location":"changes/#revision-2023-06-23","text":"Distinct inline explanation. Module sections.","title":"Revision 2023-06-23"},{"location":"changes/#revision-2023-06-11","text":"Updated list of attributes. Better @if examples.","title":"Revision 2023-06-11"},{"location":"changes/#revision-2023-06-10","text":"Updated with @if directive.","title":"Revision 2023-06-10"},{"location":"changes/#revision-2023-06-03","text":"Added specific compiletime page. Updated examples with references. variant -> any,","title":"Revision 2023-06-03"},{"location":"changes/#revision-2023-06-05","text":"Chapter on dynamic code. Updated some pages with references to dynamic calls.","title":"Revision 2023-06-05"},{"location":"changes/#revision-2023-06-02","text":"Suffix '?' replaced by '!' and vice versa. Updates to ct statements. Fixes to array descriptions Replace try? / catch? with macros Updates to reflection define/typedef is now \"def\"","title":"Revision 2023-06-02"},{"location":"changes/#revision-2023-03-18","text":"try? / catch? println -> printn","title":"Revision 2023-03-18"},{"location":"changes/#revision-2023-02-15","text":"Added uint128 / int128 to the documentation. @private, @public and @local","title":"Revision 2023-02-15"},{"location":"changes/#revision-2023-02-14","text":"Updated typedef / define","title":"Revision 2023-02-14"},{"location":"changes/#revision-2023-02-11","text":"Changed @extname to @extern following compiler changes. Document @export","title":"Revision 2023-02-11"},{"location":"changes/#revision-2023-01-24","text":"Consistently use printfn rather than printfln Added short function syntax. Added lambdas .","title":"Revision 2023-01-24"},{"location":"changes/#revision-2023-01-07","text":"Direct download links added. Prefer \"String\" over char[]","title":"Revision 2023-01-07"},{"location":"changes/#revision-2022-12-28","text":"Bitstruct documented.","title":"Revision 2022-12-28"},{"location":"changes/#revision-2022-11-18","text":"Documented inline asm.","title":"Revision 2022-11-18"},{"location":"changes/#revision-2022-11-16","text":"Move description of static initialize. Info on operator overload. More details on function pointers. Updated project tree.","title":"Revision 2022-11-16"},{"location":"changes/#revision-2022-11-07","text":"Added information on enum associated values.","title":"Revision 2022-11-07"},{"location":"changes/#revision-2022-10-31","text":"Updated project.json description. Added information about static initializers and finalizers.","title":"Revision 2022-10-31"},{"location":"changes/#revision-2022-10-14","text":"libc::printf replaced with io::printf","title":"Revision 2022-10-14"},{"location":"changes/#revision-2022-10-01","text":"Expanded and updated types .","title":"Revision 2022-10-01"},{"location":"changes/#revision-2022-07-20","text":"Added start + len syntax","title":"Revision 2022-07-20"},{"location":"changes/#revision-2022-07-15","text":"Added \"builtins\"","title":"Revision 2022-07-15"},{"location":"changes/#revision-2022-06-30","text":"Updates to module system","title":"Revision 2022-06-30"},{"location":"changes/#revision-2022-04-05","text":"Removal of @opaque .","title":"Revision 2022-04-05"},{"location":"changes/#revision-2022-03-26","text":"Remove escape macros. Adding stringify and first class names. Removing \"?? jump\"","title":"Revision 2022-03-26"},{"location":"changes/#revision-2022-03-15","text":"Removal of multi-line strings","title":"Revision 2022-03-15"},{"location":"changes/#revision-2022-03-04","text":"Updates to $sizeof. Addition of $eval / $evaltype. Removal of $unreachable.","title":"Revision 2022-03-04"},{"location":"changes/#revision-2022-02-16","text":"Updates to imports. Updates to project files.","title":"Revision 2022-02-16"},{"location":"changes/#revision-2022-02-09","text":"Major revision to bring everything up to date.","title":"Revision 2022-02-09"},{"location":"changes/#revision-2021-10-20","text":"func replaced by fn Compound literal now Type { ... } like C++. Update of conversion rules New error syntax","title":"Revision 2021-10-20"},{"location":"changes/#revision-2021-08-27","text":"Updated reflection functionality. Added documentation for multi-line strings. Added documentation for base64 and hex array literals.","title":"Revision 2021-08-27"},{"location":"changes/#revision-2021-08-12","text":"Updated error type and error handling with try/catch","title":"Revision 2021-08-12"},{"location":"changes/#revision-2021-07-13","text":"Added nesting to / ... / removed /+ ... +/ Added primer.","title":"Revision 2021-07-13"},{"location":"changes/#revision-2021-06-20","text":"Updated array layout. Revised macros for foreach. Removed old generic functions. Added new ideas around generic, macros Changed macro body definition syntax. Introduced both $for and $foreach.","title":"Revision 2021-06-20"},{"location":"changes/#revision-2021-05-31","text":"Removal of vararray type. Updated user defined attributes. Removed incremental arrays. Added information on define . Added private modules and import.","title":"Revision 2021-05-31"},{"location":"changes/#revision-2021-05-18","text":"Change cast to (type)(expression)","title":"Revision 2021-05-18"},{"location":"changes/#revision-2021-05-08","text":"Added rationale for some changes from C. Updated undefined and undefined behaviour . Removed many of the fine-grained module features. Removed \"local\" visibility in modules . All modules are now distinct, parent modules do not have any special access to submodules. Added as module imports.","title":"Revision 2021-05-08"},{"location":"changes/#revision-2021-04-05","text":"\"next\" is now \"nextcase\". Added link to the C3 discord. The conversions page updated with new conversion rules. Updated compound literal syntax. Removed undefined behaviour behaviour on integer overflow and added a list of unspecified behaviour.","title":"Revision 2021-04-05"},{"location":"changes/#revision-2020-12-23","text":"Updated slice behaviour. Updated expression block syntax. Added link to specification-in-progress.","title":"Revision 2020-12-23"},{"location":"changes/#revision-2020-12-04","text":"Local variables are implicitly zero. Removed in-block declarations. Changed struct member initialization syntax. Changed named parameter syntax. Updated on macro syntax. Removed built in c types.","title":"Revision 2020-12-04"},{"location":"changes/#revision-2020-08-22","text":"Added slice operations. Changed cast syntax to cast( as ) .","title":"Revision 2020-08-22"},{"location":"changes/#revision-2020-07-08","text":"Additions to error handling . Introduction of labelled nextcase , break and continue . Removal of goto .","title":"Revision 2020-07-08"},{"location":"changes/#revision-2020-06-17","text":"Alternate casts in idea . Method functions simply renamed to \"method\". Completely revised error handling .","title":"Revision 2020-06-17"},{"location":"changes/#revision-2020-04-23","text":"Updated error handling, adding try-else-jump and changed how errors are passed. Included reflection page","title":"Revision 2020-04-23"},{"location":"changes/#revision-2020-03-30","text":"Added Odin and D to comparisons. Updated text on how to contribute. Updated the example on undefined behaviour. Updated text on conversions. Moved double -> float conversion to \"ideas\" Fixed some typos.","title":"Revision 2020-03-30"},{"location":"changes/#revision-2020-03-29","text":"Type inference for enums. Included macro page. Corrected precedence rules with try and @ . Type functions. Managed variables back to ideas. Volatile moved back to ideas. Removed implicit lossy signed conversions. Introducing safe signed-unsigned comparisons. \"Function block\" renamed \"expression block\". @ sigil removed from macros and is only used with macro invocations. Changed cast syntax from @cast(Type, var) to cast(var, Type)","title":"Revision 2020-03-29"},{"location":"changes/#revision-2019-12-26","text":"Added module versioning system idea . Fleshed out polymorphic functions. Unsigned to signed promotion mentioned in \"changes from C\"","title":"Revision 2019-12-26"},{"location":"changes/#revision-2019-12-25","text":"Changes how generic modules work. Switched so that vararrays use Type[*] and sub arrays use Type[] . Added submodule granularity, partial imports (only importing selected functions and types), removal of local , extended aliasing. See modules . Updated \"changes from C\" with removal of multiple declarations.","title":"Revision 2019-12-25"},{"location":"changes/#revision-2019-12-11","text":"Updated the setup page.","title":"Revision 2019-12-11"},{"location":"changes/#revision-2019-12-03","text":"Added page on conversions . Added page on undefined behaviour .","title":"Revision 2019-12-03"},{"location":"changes/#revision-2019-11-01","text":"Updated \"changes from C\" with the lack of array decays. Added FourCC to the language Added name alias to ideas Added align asserts to ideas Added built in tests to ideas Added arrays page Added function blocks to statements page . Added expressions page . Added variables page . Moved managed pointers from idea to the variables page .","title":"Revision 2019-11-01"},{"location":"changes/#revision-2019-09-30","text":"Removed references (non-nullable pointers) Removed idea with aliasing in import","title":"Revision 2019-09-30"},{"location":"changes/#revision-2019-08-14","text":"Compile time run-include and include ideas. New module system idea.","title":"Revision 2019-08-14"},{"location":"changes/#revision-2019-08-14_1","text":"Namespace separator changed to :: instead of . to simplify parsing. Added FourCC, Macro text interpolation to ideas. Added Yacc grammar (incomplete) Added \"attribute\" keyword. Changed type alias declaration to use typedef ... as ... . Introduced type operator. Added section about attributes.","title":"Revision 2019-08-14"},{"location":"changes/#revision-2019-08-02","text":"Added error example. Added generics example. Added method function example. Added idea implicit method functions Expanded the types page somewhat.","title":"Revision 2019-08-02"},{"location":"changes/#revision-2019-07-30","text":"Added default and named arguments to the functions page . Added varargs to the functions page . Added idea about hierarchal memory. Added idea of raw dynamic safe arrays & strings. Volatile sections are no longer prefixed by '@' Added idea regarding c3 interop Added page about c interop . Removed c_ichar and c_uchar types as they are redundant. Updates to keywords on the grammar page ../syntax).","title":"Revision 2019-07-30"},{"location":"changes/#revision-2019-07-27","text":"Updated grammar with keywords. Added the docs & comments page. Updated the pre- and post-conditions .","title":"Revision 2019-07-27"},{"location":"changes/#revision-2019-07-24","text":"Idea: typed varargs. Added \"pure\" post condition Updated c3c commands. Removed the type keyword for defining union/struct/enum/error.","title":"Revision 2019-07-24"},{"location":"changes/#revision-2019-07-23","text":"Added to generic functions examples for [] and []= Developed ideas about vectors in the idea section . Defined 2's complement for signed integers. Idea: Managed pointers. Updated naming rules for types. Added more naming rules + examples of them. Removed \"defer on function signatures\" from ideas. Removed \"managed qualifier\" from ideas. Removed \"defer sugar\" from ideas. Removed \"built in dynamic arrays\" from ideas. Added standard_library section. Added more about pre- and post-conditions .","title":"Revision 2019-07-23"},{"location":"changes/#revision-2019-07-22","text":"Added \"Design Principles\" to the index page.","title":"Revision 2019-07-22"},{"location":"changes/#revision-2019-07-21","text":"\"return\" rather than function name is used in post conditions. See Functions Added \"@include\" macro for textual includes. See Modules . Files to without module for single file compilations is now ok as a special case. See Modules Added cone style array idea to the idea section . Added idea about defer on error to the idea section . Added idea for aliasing generic structs in the import to the idea section . Added idea for changing automatic signed <-> unsigned conversion to the idea section . Added Changes from C and Statements sections. Removal of volatile . See Changes from C and Statements Removal of const See Changes from C","title":"Revision 2019-07-21"},{"location":"changesfromc/","text":"Changes from C Although C3 is trying to improve on C, this does not only mean addition of features, but also removal, or breaking changes: No mandatory header files There is a C3 interchange header format for declaring interfaces of libraries, but it is only used for special applications. Removal of the old C macro system The old C macro system is replaced by a new C3 macro system. Import and modules C3 uses module imports instead of header includes to link modules together. Member access using . even for pointers The -> operator is removed, access uses dot for both direct and pointer access. Note that this is just single access: to access a pointer of a pointer (e.g. int** ) an explicit dereference would be needed. Different operator precedence Notably bit operations have higher precedence than +/-, making code like this: a & b == c evaluate like (a & b) == c instead of C's a & (b == c) . See the page about precedence rules . Removal of the const type qualifier The const qualifier is only retained for actual constant variables. C3 uses a special type of post condition for functions to indicate that they do not alter in parameters. /** * This function ensures that foo is not changed in the function. * @param [in] foo * @param [out] bar **/ fn void test(Foo* foo, Bar* bar) { bar.y = foo.x; // foo.x = foo.x + 1 - compile time error, can't write to 'in' param. // int x = bar.y - compile time error, can't read from an 'out' param. } Rationale: const correctness requires littering const across the code base. Although const is useful, it provides weaker guarantees that it appears. Fixed arrays do not decay and have copy semantics C3 has three different array types. Variable arrays and slices decay to pointers, but fixed arrays are value objects and do not decay. int[3] a = { 1, 2, 3 }; int[4]* b = &a; // No conversion int* c = a; // ERROR int* d = &a; // Valid implicit conversion int* e = b; // Valid implicit conversion int[3] f = a; // Copy by value! Removal of multiple declaration syntax with initialization Only a single declaration with initialization is allowed per statement in C3: int i, j = 1; // ERROR int a = 1; // Ok int b, c; // Ok In conditionals, a special form of multiple declarations are allowed but each must then provide its type: for (int i = 0, int j = 1; i < 10; i++, j++) { ... } Integer promotions rules and safe signed-unsigned comparisons Promotion rules for integer types are different from C. C3 allows implicit widening only where there is only a single way to widen the expression. To explain the latter: take the case of long x = int_val_1 + int_val_2 . In C this would widen the result of the addition: long x = (long)(int_val_1 + int_val_2) , but there is another possible way to widen: long x = (long)int_val_1 + (long)int_val_2 . so in this case, the widening is disallowed. However, long x = int_val_1 is unambiguous, so C3 permits it just like C (read more on the conversion page . C3 also adds safe signed-unsigned comparisons : this means that comparing signed and unsigned values will always yield the correct result: // The code below would print \"Hello C3!\" in C3 and \"Hello C!\" in C. int i = -1; uint j = 1; if (i < j) { printf(\"Hello C3!\\n\"); } else { printf(\"Hello C!\\n\"); } Goto removed goto is removed and replaced with labelled break and continue together with the nextcase statement that allows you to jump between cases in a switch statement. Rationale: It is very difficult to make goto work well with defer and implicit unwrapping of optional results. It is not just making the compiler harder to write, but the code is harder to understand as well. The replacements together with defer cover many if not all usages of goto in regular code. Implicit break in switches Empty case statements have implicit fall through in C3, otherwise the nextcase statement is needed nextcase can also be used to jump to any other case statement in the switch. switch (h) { case 1: a = 1; nextcase; // Fall through case 2: b = 123; case 3: a = 2; nextcase 2; // Jump to case 2 default: a = 111; } Locals variables are implicitly zeroed In C global variables are implicitly zeroed out, but local variables aren't. In C3 local variables are zeroed out by default, but may be explicitly undefined 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 style: call_foo((Foo) { 1, 2, 3 }); // C++ style (1): call_foo(Foo(1, 2, 3)); // C++ style (2): call_foo(Foo { 1, 2, 3 }); // C3: call_foo(Foo { 1, 2, 3 } ); // C3 with inference: call_foo({ 1, 2, 3 }); Bitfields replaced by bitstructs Bitfields are replaced by bitstructs that have a well-defined encapsulating type, and an exact bit layout. // C struct Foo { int a : 3; unsigned b : 4; MyEnum c : 7; }; struct Flags { bool has_hyperdrive : 1; bool has_tractorbeam : 1; bool has_plasmatorpedoes : 1; } // C3 bitstruct Foo : short { int a : 0..2; uint b : 3..6; MyEnum c : 7..13; } // Simple form, only allowed when all fields are bools. struct Flags : char { bool has_hyperdrive; bool has_tractorbeam; bool has_plasmatorpedoes; } Evaluation order is well-defined Evaluation order is left-to-right, and in assignment expressions, assignment happens after expression evaluation. Signed overflow is well-defined Signed integer overflow always wraps using 2s complement. It is never undefined behaviour.","title":"Changes from C"},{"location":"changesfromc/#changes-from-c","text":"Although C3 is trying to improve on C, this does not only mean addition of features, but also removal, or breaking changes:","title":"Changes from C"},{"location":"changesfromc/#no-mandatory-header-files","text":"There is a C3 interchange header format for declaring interfaces of libraries, but it is only used for special applications.","title":"No mandatory header files"},{"location":"changesfromc/#removal-of-the-old-c-macro-system","text":"The old C macro system is replaced by a new C3 macro system.","title":"Removal of the old C macro system"},{"location":"changesfromc/#import-and-modules","text":"C3 uses module imports instead of header includes to link modules together.","title":"Import and modules"},{"location":"changesfromc/#member-access-using-even-for-pointers","text":"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.","title":"Member access using . even for pointers"},{"location":"changesfromc/#different-operator-precedence","text":"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 .","title":"Different operator precedence"},{"location":"changesfromc/#removal-of-the-const-type-qualifier","text":"The const qualifier is only retained for actual constant variables. C3 uses a special type of post condition for functions to indicate that they do not alter in parameters. /** * This function ensures that foo is not changed in the function. * @param [in] foo * @param [out] bar **/ fn void test(Foo* foo, Bar* bar) { bar.y = foo.x; // foo.x = foo.x + 1 - compile time error, can't write to 'in' param. // int x = bar.y - compile time error, can't read from an 'out' param. } Rationale: const correctness requires littering const across the code base. Although const is useful, it provides weaker guarantees that it appears.","title":"Removal of the const type qualifier"},{"location":"changesfromc/#fixed-arrays-do-not-decay-and-have-copy-semantics","text":"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!","title":"Fixed arrays do not decay and have copy semantics"},{"location":"changesfromc/#removal-of-multiple-declaration-syntax-with-initialization","text":"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++) { ... }","title":"Removal of multiple declaration syntax with initialization"},{"location":"changesfromc/#integer-promotions-rules-and-safe-signed-unsigned-comparisons","text":"Promotion rules for integer types are different from C. C3 allows implicit widening only where there is only a single way to widen the expression. To explain the latter: take the case of long x = int_val_1 + int_val_2 . In C this would widen the result of the addition: long x = (long)(int_val_1 + int_val_2) , but there is another possible way to widen: long x = (long)int_val_1 + (long)int_val_2 . so in this case, the widening is disallowed. However, long x = int_val_1 is unambiguous, so C3 permits it just like C (read more on the conversion page . C3 also adds safe signed-unsigned comparisons : this means that comparing signed and unsigned values will always yield the correct result: // The code below would print \"Hello C3!\" in C3 and \"Hello C!\" in C. int i = -1; uint j = 1; if (i < j) { printf(\"Hello C3!\\n\"); } else { printf(\"Hello C!\\n\"); }","title":"Integer promotions rules and safe signed-unsigned comparisons"},{"location":"changesfromc/#goto-removed","text":"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.","title":"Goto removed"},{"location":"changesfromc/#implicit-break-in-switches","text":"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; }","title":"Implicit break in switches"},{"location":"changesfromc/#locals-variables-are-implicitly-zeroed","text":"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.","title":"Locals variables are implicitly zeroed"},{"location":"changesfromc/#compound-literal-syntax-changed","text":"// 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 });","title":"Compound literal syntax changed"},{"location":"changesfromc/#bitfields-replaced-by-bitstructs","text":"Bitfields are replaced by bitstructs that have a well-defined encapsulating type, and an exact bit layout. // C struct Foo { int a : 3; unsigned b : 4; MyEnum c : 7; }; struct Flags { bool has_hyperdrive : 1; bool has_tractorbeam : 1; bool has_plasmatorpedoes : 1; } // C3 bitstruct Foo : short { int a : 0..2; uint b : 3..6; MyEnum c : 7..13; } // Simple form, only allowed when all fields are bools. struct Flags : char { bool has_hyperdrive; bool has_tractorbeam; bool has_plasmatorpedoes; }","title":"Bitfields replaced by bitstructs"},{"location":"changesfromc/#evaluation-order-is-well-defined","text":"Evaluation order is left-to-right, and in assignment expressions, assignment happens after expression evaluation.","title":"Evaluation order is well-defined"},{"location":"changesfromc/#signed-overflow-is-well-defined","text":"Signed integer overflow always wraps using 2s complement. It is never undefined behaviour.","title":"Signed overflow is well-defined"},{"location":"cinterop/","text":"C Interoperability 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.","title":"C Interop"},{"location":"cinterop/#c-interoperability","text":"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)); }","title":"C Interoperability"},{"location":"cinterop/#linking-static-and-dynamic-libraries","text":"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/\"] )","title":"Linking static and dynamic libraries"},{"location":"cinterop/#gotchas","text":"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.","title":"Gotchas"},{"location":"comments/","text":"Comments and docs C3 uses three distinct comment types: The normal // line comment, which is terminated at the end of the line. The classic /* ... */ C style comment, but unlike in C they are allowed to nest. Documentation comments /** ... **/ the text within these comments will be parsed as documentation and optional contracts on the following code. 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 for information regarding @require , @ensure , @const , @pure , @checked .","title":"Comments & Docs"},{"location":"comments/#comments-and-docs","text":"C3 uses three distinct comment types: The normal // line comment, which is terminated at the end of the line. The classic /* ... */ C style comment, but unlike in C they are allowed to nest. Documentation comments /** ... **/ the text within these comments will be parsed as documentation and optional contracts on the following code.","title":"Comments and docs"},{"location":"comments/#documentation","text":"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.","title":"Documentation"},{"location":"comments/#available-annotations","text":"Name format @param @param @return @return @return! @return! , , ... @fails @fails @deprecated @deprecated @require @require , , ... @ensure @ensure , , ... @pure @pure See contracts for information regarding @require , @ensure , @const , @pure , @checked .","title":"Available annotations"},{"location":"compare/","text":"Language comparisons 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 \u2013 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 \u2013 like an implicit context \u2013 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.","title":"Comparisons with other languages"},{"location":"compare/#language-comparisons","text":"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.","title":"Language comparisons"},{"location":"compare/#c","text":"As C3 is an evolution of C, the languages are quite similar. C3 adds features, but also removes a few.","title":"C"},{"location":"compare/#in-c3-but-not-in-c","text":"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","title":"In C3 but not in C"},{"location":"compare/#in-c-but-not-in-c3","text":"Qualified types ( const , volatile etc) Unsafe implicit conversions","title":"In C but not in C3"},{"location":"compare/#c_1","text":"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 \u2013 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.","title":"C++"},{"location":"compare/#in-c-but-not-in-c3_1","text":"Objects and classes RAII Exceptions","title":"In C++ but not in C3"},{"location":"compare/#in-c3-but-not-in-c_1","text":"Module system (yet) Integrated build system Semantic macros Error handling Defer Associated enum data Built-in subarrays Dynamic calls","title":"In C3 but not in C++"},{"location":"compare/#rust","text":"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.","title":"Rust"},{"location":"compare/#in-rust-but-not-in-c3","text":"RAII Memory safety Safe union types with functions Different syntax from C Pattern matching Async built in","title":"In Rust but not in C3"},{"location":"compare/#in-c3-but-not-in-rust","text":"Same ease of programming as C Optional contracts Familiar C syntax and behaviour Dynamic calls","title":"In C3 but not in Rust"},{"location":"compare/#zig","text":"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.","title":"Zig"},{"location":"compare/#in-zig-but-not-in-c3","text":"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.","title":"In Zig but not in C3"},{"location":"compare/#in-c3-but-not-in-zig","text":"Module system. Integrated build system. C ABI compatibility by default. Optional contracts. Familiar C syntax and behaviour. Dynamic interfaces. Built in benchmarks.","title":"In C3 but not in Zig"},{"location":"compare/#jai","text":"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.","title":"Jai"},{"location":"compare/#in-jai-but-not-in-c3","text":"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.","title":"In Jai but not in C3"},{"location":"compare/#in-c3-but-not-in-jai","text":"Module system. Integrated build system. Optional contracts. Familiar C syntax and behaviour. Fairly small language. Dynamic interfaces.","title":"In C3 but not in Jai"},{"location":"compare/#odin","text":"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 \u2013 like an implicit context \u2013 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.","title":"Odin"},{"location":"compare/#in-odin-but-not-in-c3","text":"Different syntax and behaviour compared to C. Ad hoc parametric polymorphism. Tuple returns. A rich built in set of types.","title":"In Odin but not in C3"},{"location":"compare/#in-c3-but-not-in-odin","text":"Familiar C syntax and behaviour. Semantic macros. Value methods. Optional contracts. Error handling support. Dynamic interfaces.","title":"In C3 but not in Odin"},{"location":"compare/#d","text":"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.","title":"D"},{"location":"compare/#in-d-but-not-in-c3","text":"Objects and classes. RAII. Exceptions. Optional GC. + Many, many more features.","title":"In D but not in C3"},{"location":"compare/#in-c3-but-not-in-d","text":"Fairly small language.","title":"In C3 but not in D"},{"location":"compiletime/","text":"Compile time introspection and execution 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: Non-conditional declarations are registered. Conditional module sections are either discarded or have all of their non-conditional declarations registered. Each module in turn will evaluate @if attributes for each module section. The order of module and module section evaluation in (2) and (3) is not deterministic and any use of $defined should not rely on this ordering. Compile time introspection At compile time, full type information is available. This allows for creation of reusable, code generating, macros for things like serialization. usz foo_alignment = Foo.alignof; usz foo_member_count = Foo.membersof.len; String foo_name = Foo.nameof; To read more about all the fields available at compile time, see the page on reflection .","title":"Compile time evaluation"},{"location":"compiletime/#compile-time-introspection-and-execution","text":"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.","title":"Compile time introspection and execution"},{"location":"compiletime/#compile-time-values","text":"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.","title":"Compile time values"},{"location":"compiletime/#if-and-switch","text":"$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 }","title":"$if and $switch"},{"location":"compiletime/#loops-using-foreach-and-for","text":"$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.","title":"Loops using $foreach and $for"},{"location":"compiletime/#compile-time-macro-execution","text":"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.","title":"Compile time macro execution"},{"location":"compiletime/#conditional-compilation-at-the-top-level-using-if","text":"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","title":"Conditional compilation at the top level using @if"},{"location":"compiletime/#evaluation-order-of-top-level-conditional-compilation","text":"Conditional compilation at the top level can cause unexpected ordering issues, especially when combined with $defined . At a high level, there are three phases of evaluation: Non-conditional declarations are registered. Conditional module sections are either discarded or have all of their non-conditional declarations registered. Each module in turn will evaluate @if attributes for each module section. The order of module and module section evaluation in (2) and (3) is not deterministic and any use of $defined should not rely on this ordering.","title":"Evaluation order of top level conditional compilation"},{"location":"compiletime/#compile-time-introspection","text":"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 .","title":"Compile time introspection"},{"location":"contracts/","text":"Contracts 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 \u2013 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(); }","title":"Contracts"},{"location":"contracts/#contracts","text":"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 \u2013 even if a potential bug may cause them to be violated.","title":"Contracts"},{"location":"contracts/#pre-conditions","text":"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; }","title":"Pre-conditions"},{"location":"contracts/#post-conditions","text":"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); }","title":"Post conditions"},{"location":"contracts/#parameter-annotations","text":"@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 }","title":"Parameter annotations"},{"location":"contracts/#pure-in-detail","text":"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.","title":"Pure in detail"},{"location":"contracts/#pre-conditions-for-macros","text":"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(); }","title":"Pre conditions for macros"},{"location":"contribute/","text":"The C3 language is still in its design phase , which means functionality and specification are subject to change. That also means that any contribution right now will have a big impact on the language. So if you find the project interesting, here's what you can do to help: Discuss the language on the C3 Discord https://discord.gg/qN76R87 Suggest improvements by filing an issue: https://github.com/c3lang/c3docs/issues/new Offer to work on the compiler being written here: https://github.com/c3lang/c3c","title":"Contribute"},{"location":"conversion/","text":"Conversions and promotions 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: For any floating point type with a bitwidth smaller than 32 bits, widen to float . E.g. f16 -> float For an integer type smaller than the minimum arithmetic width promote the value to a same signed integer of the minimum arithmetic width (this usually corresponds to a c int/uint). E.g. ushort -> uint Implicit narrowing An expression with an integer type, may implicitly narrow to smaller integer type, and similarly a float type may implicitly narrow to less wide floating point type is determined from the following algorithm. Shifts and assign look at the lhs expression. ++ , -- , ~ , - , !! , ! - check the inner type. + , - , * , / , % , ^ , | , & , ?? , ?: - check both lhs and rhs. Narrowing int/float cast, assume the type is the narrowed type. Widening int/float cast, look at the inner expression, ignoring the cast. In the case of any other cast, assume it is opaque and the type is that of the cast. In the case of an integer literal, instead of looking at the type, check that the integer would fit the type to narrow to. For .len access, allow narrowing to C int width. For all other expressions, check against the size of the type. As rough guide: if all the sub expressions originally are small enough it's ok to implicitly convert the result. Examples float16 h = 12.0; float f = 13.0; double d = 22.0; char x = 1; short y = -3; int z = 0xFFFFF; ulong w = -0xFFFFFFF; x = x + x; // => calculated as x = (char)((int)x + (int)x); x = y + x; // => Error, narrowing not allowed as y > char h = x * h; // => calculated as h = (float16)((float)x * (float)h); h = f + x; // => Error, narrowing not allowed since f > f16 Implicit widening Unlike C, implicit widening will only happen on \"simple expressions\": if the expression is a primary expression, or a unary operation on a primary expression. For assignment, special rules hold. For an assignment to a binary expression, if its two subexpressions are \"simple expressions\" and the binary expression is + , - , / , * , allow an implicit promotion of the two sub expressions. int a = ... short b = ... char c = ... long d = a; // Valid - simple expression. int e = (int)(d + (a + b)); // Error int f = (int)(d + ~b); // Valid long g = a + b; // Valid As a rule of thumb, if there are more than one possible conversion an explicit cast is needed. Example: long h = a + (b + c); // Possible intention 1 long h = (long)(a + (b + c)); // Possible intention 2 long h = (long)a + (long)(b + c); // Possible intention 3 long h = (long)a + ((long)b + (long)c); Maximum type The maximum type is a concept used when unifying two or more types. The algorithm follows: First perform implicit promotion. If both types are the same, the maximum type is this type. If one type is a floating point type, and the other is an integer type, the maximum type is the floating point type. E.g. int + float -> float . If both types are floating point types, the maximum type is the widest floating point type. E.g. float + double -> double . If both types are integer types with the same signedness, the maximum type is the widest integer type of the two. E.g. uint + ulong -> ulong . If both types are integer types with different signedness, the maximum type is a signed integer with the same bit width as the maximum integer type. ulong + int -> long If at least one side is a struct or a pointer to a struct with an inline directive on a member, check recursively check if the type of the inline member can be used to find a maximum type (see below under sub struct conversions) All other cases are errors. Substruct conversions Substructs may be used in place of its parent structs in many cases. The rule is as follows: A substruct pointer may implicitly convert to a parent struct. A substruct value may be implicitly assigned to a variable with the parent struct type, This will truncate the value, copying only the parent part of the substruct. However, a substruct value cannot be assigned its parent struct. Substruct 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 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. Resolve the operands. Find the maximum type of the two operands. Promote both operands to the resulting type if both are simple expressions The resulting type of the expression is the resulting type. 2. Addition with left side being a pointer Resolve the operands. If the rhs is not an integer, this is an error. If the rhs has a bit width that exceeds isz, this is an error. The result of the expression is the lhs type. 3. Subtraction with lhs pointer and rhs integer Resolve the operands. If the right hand type has a bit width that exceeds isz, this is an error. The result of the expression is the left hand type. 4. Subtraction with both sides pointers Resolve the operands. If the either side is a void * , it is cast to the other type. If the types of the sides are different, this is an error. The result of the expression is isz. If this result exceeds the target width, this is an error. 6. Bit operations ^ & | These operations are only valid for integers and booleans. Resolve the operands. Find the maximum type of the two operands. Promote both operands to the maximum type if they are simple expressions. The result of the expression is the maximum type. 6. Shift operations << >> These operations are only valid for integers. Resolve the operands. In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. 3The result of the expression is the lhs type. 7. Assignment operations += -= *= *= /= %= ^= |= &= Resolve the lhs. Resolve the right operand as an assignment rhs. The result of the expression is the lhs type. 8. Assignment shift >>= <<= Resolve both operands In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. The result of the expression is the lhs type. 9. && and || Resolve both operands. Insert bool cast of both operands. The type is bool. 10. <= == >= != Resolve the operands, left to right. Find the maximum type of the two operands. Promote both operands to the maximum type. The type is bool. Unary conversions 1. Bit negate Resolve the inner operand. If the inner type is not an integer this is an error. The type is the inner type. 2. Boolean not Resolve the inner operand. The type is bool. 3. Negation Resolve the inner operand. If the type inner type is not a number this is an error. If the inner type is an unsigned integer, cast it to the same signed type. The type is the type of the result from (3) 4. & and && Resolve the inner operand. The type is a pointer to the type of the inner operand. 5. * Resolve the inner operand. If the operand is not a pointer, or is a void * pointer, this is an error. The type is the pointee of the inner operand's type. Dereferencing 0 is implementation defined. 6. ++ and -- Resolve the inner operand. If the type is not a number, this is an error. The type is the same as the inner operand. Base expressions 1. Typed identifiers The type is that of the declaration. If the width of the type is less than that of the target type, widen to the target type. If the width of the type is greater than that of the target type, it is an error. 2. Constants and literals If the constant is an integer, it is assumed to be the arithmetic promotion width and signed. If the suffix u is added, it is assumed to be an unsigned number. If a suffix ixx or uxx is given then it is considered a an integer of that type width and signedness. It cannot be implicitly narrowed. If the constant is a floating point value, it is assumed to be a double unless suffixed 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.","title":"Conversions"},{"location":"conversion/#conversions-and-promotions","text":"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.","title":"Conversions and promotions"},{"location":"conversion/#target-type","text":"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.","title":"Target type"},{"location":"conversion/#common-arithmetic-promotion","text":"Like C, C3 uses implicit arithmetic promotion of integer and floating point variables before arithmetic operations: For any floating point type with a bitwidth smaller than 32 bits, widen to float . E.g. f16 -> float For an integer type smaller than the minimum arithmetic width promote the value to a same signed integer of the minimum arithmetic width (this usually corresponds to a c int/uint). E.g. ushort -> uint","title":"Common arithmetic promotion"},{"location":"conversion/#implicit-narrowing","text":"An expression with an integer type, may implicitly narrow to smaller integer type, and similarly a float type may implicitly narrow to less wide floating point type is determined from the following algorithm. Shifts and assign look at the lhs expression. ++ , -- , ~ , - , !! , ! - check the inner type. + , - , * , / , % , ^ , | , & , ?? , ?: - check both lhs and rhs. Narrowing int/float cast, assume the type is the narrowed type. Widening int/float cast, look at the inner expression, ignoring the cast. In the case of any other cast, assume it is opaque and the type is that of the cast. In the case of an integer literal, instead of looking at the type, check that the integer would fit the type to narrow to. For .len access, allow narrowing to C int width. For all other expressions, check against the size of the type. As rough guide: if all the sub expressions originally are small enough it's ok to implicitly convert the result. Examples float16 h = 12.0; float f = 13.0; double d = 22.0; char x = 1; short y = -3; int z = 0xFFFFF; ulong w = -0xFFFFFFF; x = x + x; // => calculated as x = (char)((int)x + (int)x); x = y + x; // => Error, narrowing not allowed as y > char h = x * h; // => calculated as h = (float16)((float)x * (float)h); h = f + x; // => Error, narrowing not allowed since f > f16","title":"Implicit narrowing"},{"location":"conversion/#implicit-widening","text":"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);","title":"Implicit widening"},{"location":"conversion/#maximum-type","text":"The maximum type is a concept used when unifying two or more types. The algorithm follows: First perform implicit promotion. If both types are the same, the maximum type is this type. If one type is a floating point type, and the other is an integer type, the maximum type is the floating point type. E.g. int + float -> float . If both types are floating point types, the maximum type is the widest floating point type. E.g. float + double -> double . If both types are integer types with the same signedness, the maximum type is the widest integer type of the two. E.g. uint + ulong -> ulong . If both types are integer types with different signedness, the maximum type is a signed integer with the same bit width as the maximum integer type. ulong + int -> long If at least one side is a struct or a pointer to a struct with an inline directive on a member, check recursively check if the type of the inline member can be used to find a maximum type (see below under sub struct conversions) All other cases are errors.","title":"Maximum type"},{"location":"conversion/#substruct-conversions","text":"Substructs may be used in place of its parent structs in many cases. The rule is as follows: A substruct pointer may implicitly convert to a parent struct. A substruct value may be implicitly assigned to a variable with the parent struct type, This will truncate the value, copying only the parent part of the substruct. However, a substruct value cannot be assigned its parent struct. Substruct subarrays, vararrays and arrays can not be cast (implicitly or explicitly) to an array of the parent struct type.","title":"Substruct conversions"},{"location":"conversion/#pointer-conversions","text":"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","title":"Pointer conversions"},{"location":"conversion/#vector-conversions","text":"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] .","title":"Vector conversions"},{"location":"conversion/#binary-conversions","text":"","title":"Binary conversions"},{"location":"conversion/#1-multiplication-division-remainder-subtraction-addition-with-both-operands-being-numbers","text":"These operations are only valid for integer and float types. Resolve the operands. Find the maximum type of the two operands. Promote both operands to the resulting type if both are simple expressions The resulting type of the expression is the resulting type.","title":"1. Multiplication, division, remainder, subtraction / addition with both operands being numbers"},{"location":"conversion/#2-addition-with-left-side-being-a-pointer","text":"Resolve the operands. If the rhs is not an integer, this is an error. If the rhs has a bit width that exceeds isz, this is an error. The result of the expression is the lhs type.","title":"2. Addition with left side being a pointer"},{"location":"conversion/#3-subtraction-with-lhs-pointer-and-rhs-integer","text":"Resolve the operands. If the right hand type has a bit width that exceeds isz, this is an error. The result of the expression is the left hand type.","title":"3. Subtraction with lhs pointer and rhs integer"},{"location":"conversion/#4-subtraction-with-both-sides-pointers","text":"Resolve the operands. If the either side is a void * , it is cast to the other type. If the types of the sides are different, this is an error. The result of the expression is isz. If this result exceeds the target width, this is an error.","title":"4. Subtraction with both sides pointers"},{"location":"conversion/#6-bit-operations","text":"These operations are only valid for integers and booleans. Resolve the operands. Find the maximum type of the two operands. Promote both operands to the maximum type if they are simple expressions. The result of the expression is the maximum type.","title":"6. Bit operations ^ & |"},{"location":"conversion/#6-shift-operations","text":"These operations are only valid for integers. Resolve the operands. In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. 3The result of the expression is the lhs type.","title":"6. Shift operations << >>"},{"location":"conversion/#7-assignment-operations-","text":"Resolve the lhs. Resolve the right operand as an assignment rhs. The result of the expression is the lhs type.","title":"7. Assignment operations += -= *= *= /= %= ^= |= &="},{"location":"conversion/#8-assignment-shift","text":"Resolve both operands In safe mode, insert a trap to ensure that rhs >= 0 and rhs < bit width of the left hand side. The result of the expression is the lhs type.","title":"8. Assignment shift >>= <<="},{"location":"conversion/#9-and","text":"Resolve both operands. Insert bool cast of both operands. The type is bool.","title":"9. && and ||"},{"location":"conversion/#10","text":"Resolve the operands, left to right. Find the maximum type of the two operands. Promote both operands to the maximum type. The type is bool.","title":"10. <= == >= !="},{"location":"conversion/#unary-conversions","text":"","title":"Unary conversions"},{"location":"conversion/#1-bit-negate","text":"Resolve the inner operand. If the inner type is not an integer this is an error. The type is the inner type.","title":"1. Bit negate"},{"location":"conversion/#2-boolean-not","text":"Resolve the inner operand. The type is bool.","title":"2. Boolean not"},{"location":"conversion/#3-negation","text":"Resolve the inner operand. If the type inner type is not a number this is an error. If the inner type is an unsigned integer, cast it to the same signed type. The type is the type of the result from (3)","title":"3. Negation"},{"location":"conversion/#4-and","text":"Resolve the inner operand. The type is a pointer to the type of the inner operand.","title":"4. & and &&"},{"location":"conversion/#5","text":"Resolve the inner operand. If the operand is not a pointer, or is a void * pointer, this is an error. The type is the pointee of the inner operand's type. Dereferencing 0 is implementation defined.","title":"5. *"},{"location":"conversion/#6-and-","text":"Resolve the inner operand. If the type is not a number, this is an error. The type is the same as the inner operand.","title":"6. ++ and --"},{"location":"conversion/#base-expressions","text":"","title":"Base expressions"},{"location":"conversion/#1-typed-identifiers","text":"The type is that of the declaration. If the width of the type is less than that of the target type, widen to the target type. If the width of the type is greater than that of the target type, it is an error.","title":"1. Typed identifiers"},{"location":"conversion/#2-constants-and-literals","text":"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. 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.","title":"2. Constants and literals"},{"location":"define/","text":"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 . 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 }","title":"Define"},{"location":"define/#the-def-statement","text":"The def statement in C3 is intended for aliasing identifiers and types.","title":"The \"def\" statement"},{"location":"define/#defining-a-type-alias","text":"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);","title":"Defining a type alias"},{"location":"define/#distinct-types","text":"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","title":"Distinct types"},{"location":"define/#distinct-inline","text":"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!","title":"Distinct inline"},{"location":"define/#function-and-variable-aliases","text":"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; }","title":"Function and variable aliases"},{"location":"define/#using-def-to-create-generic-types-functions-and-variables","text":"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 .","title":"Using def to create generic types, functions and variables"},{"location":"define/#function-pointer-default-arguments-and-named-parameters","text":"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 }","title":"Function pointer default arguments and named parameters"},{"location":"examples/","text":"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 . 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 . 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 . 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: 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 . 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 Dynamic calls Runtime dynamic dispatch through interfaces: import std::io; // Define a dynamic interface interface MyName { fn String myname(); } struct Bob (MyName) { int x; } // Required implementation as Bob implements MyName fn String Bob.myname(Bob*) @dynamic { return \"I am Bob!\"; } // Ad hoc implementation fn String int.myname(int*) @dynamic { return \"I am int!\"; } fn void whoareyou(any* a) { MyName* b = (MyName*)a; if (!&b.myname) { io::printn(\"I don't know who I am.\"); return; } io::printn(b.myname()); } fn void main() { int i = 1; double d = 1.0; Bob bob; any* a = &i; whoareyou(a); a = &d; whoareyou(a); a = &bob; whoareyou(a); } Read more about dynamic calls here .","title":"Examples"},{"location":"examples/#if-statement","text":"fn void if_example(int a) { if (a > 0) { // .. } else { // .. } }","title":"if-statement"},{"location":"examples/#for-loop","text":"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 (;;) { // .. } }","title":"for-loop"},{"location":"examples/#foreach-loop","text":"fn void example_foreach(float[] values) { foreach (index, value : values) { io::printfn(\"%d: %f\", index, value); } }","title":"foreach-loop"},{"location":"examples/#while-loop","text":"fn void example_while() { // again exactly the same as C int a = 10; while (a > 0) { a--; } // Declaration while (Point* p = getPoint()) { // .. } }","title":"while-loop"},{"location":"examples/#enum-switch","text":"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];","title":"enum + switch"},{"location":"examples/#defer","text":"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","title":"defer"},{"location":"examples/#struct-types","text":"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; } }","title":"struct types"},{"location":"examples/#function-pointers","text":"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); // .. }","title":"Function pointers"},{"location":"examples/#error-handling","text":"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 .","title":"Error handling"},{"location":"examples/#contracts","text":"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 .","title":"Contracts"},{"location":"examples/#macros","text":"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 .","title":"Macros"},{"location":"examples/#methods","text":"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); }","title":"Methods"},{"location":"examples/#compile-time-reflection-and-execution","text":"Access type information and loop over values at compile time: import std::io; struct Foo { int a; double b; int* ptr; } macro print_fields($Type) { $foreach ($field : $Type.membersof) io::printfn(\"Field %s, offset: %s, size: %s, type: %s\", $field.nameof, $field.offsetof, $field.sizeof, $field.typeid.nameof); $endforeach } fn void main() { print_fields(Foo); } This prints on x64: Field a, offset: 0, size: 4, type: int Field b, offset: 8, size: 8, type: double Field ptr, offset: 16, size: 8, type: int*","title":"Compile time reflection and execution"},{"location":"examples/#compile-time-execution","text":"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 .","title":"Compile time execution"},{"location":"examples/#generic-modules","text":"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","title":"Generic modules"},{"location":"examples/#dynamic-calls","text":"Runtime dynamic dispatch through interfaces: import std::io; // Define a dynamic interface interface MyName { fn String myname(); } struct Bob (MyName) { int x; } // Required implementation as Bob implements MyName fn String Bob.myname(Bob*) @dynamic { return \"I am Bob!\"; } // Ad hoc implementation fn String int.myname(int*) @dynamic { return \"I am int!\"; } fn void whoareyou(any* a) { MyName* b = (MyName*)a; if (!&b.myname) { io::printn(\"I don't know who I am.\"); return; } io::printn(b.myname()); } fn void main() { int i = 1; double d = 1.0; Bob bob; any* a = &i; whoareyou(a); a = &d; whoareyou(a); a = &bob; whoareyou(a); } Read more about dynamic calls here .","title":"Dynamic calls"},{"location":"expressions/","text":"Expressions Expressions work like in C, with one exception: it is possible to take the address of a temporary. This uses the operator && rather than & . Consequently, this is valid: fn void test(int* x) { ... } test(&&1); // In C: // int x = 1; // test(&x); Well-defined evaluation order Expressions have a well-defined evaluation order: Binary expressions are evaluated from left to right. Assignment occurs right to left, so a = a++ would result in a being unchanged. Call arguments are evaluated in parameter order. 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: The null literal. Boolean, floating point and integer literals. The result of arithmetics on constant expressions. Compile time variables (prefixed with $ ) Global constant variables with initializers that are constant expressions. The result of macros that does not generate code and only uses constant expressions. The result of a cast if the value is cast to a boolean, floating point or integer type and the value that is converted is a constant expression. String literals. Initializer lists containing constant values. Some things that are not constant expressions: Any pointer that isn't the null literal, even if it's derived from a constant expression. The result of a cast except for casts of constant expressions to a numeric type. Compound literals - even when values are constant expressions. Including binary data The $embed(...) function includes the contents of a file into the compilation as a constant array of bytes: char[*] my_image = $embed(\"my_image.png\"); The result of an embed work similar to a string literal and can implicitly convert to a char* , void* , char[] , char[*] and String . Limiting length It's possible to limit the length of included with the optional second parameter. char[4] my_data = $embed(\"foo.txt\", 4]; Failure to load at compile time and defaults Usually it's a compile time error if the file can't be included, but sometimes it's useful to only optionally include it. If this is desired, declare the left hand side 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;","title":"Expressions"},{"location":"expressions/#expressions","text":"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);","title":"Expressions"},{"location":"expressions/#well-defined-evaluation-order","text":"Expressions have a well-defined evaluation order: Binary expressions are evaluated from left to right. Assignment occurs right to left, so a = a++ would result in a being unchanged. Call arguments are evaluated in parameter order. 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 .","title":"Well-defined evaluation order"},{"location":"expressions/#compound-literals","text":"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 }));","title":"Compound literals"},{"location":"expressions/#constant-expressions","text":"In C3 all constant expressions are guaranteed to be calculated at compile time. The following are considered constant expressions: The null literal. Boolean, floating point and integer literals. The result of arithmetics on constant expressions. Compile time variables (prefixed with $ ) Global constant variables with initializers that are constant expressions. The result of macros that does not generate code and only uses constant expressions. The result of a cast if the value is cast to a boolean, floating point or integer type and the value that is converted is a constant expression. String literals. Initializer lists containing constant values. Some things that are not constant expressions: Any pointer that isn't the null literal, even if it's derived from a constant expression. The result of a cast except for casts of constant expressions to a numeric type. Compound literals - even when values are constant expressions.","title":"Constant expressions"},{"location":"expressions/#including-binary-data","text":"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 .","title":"Including binary data"},{"location":"expressions/#limiting-length","text":"It's possible to limit the length of included with the optional second parameter. char[4] my_data = $embed(\"foo.txt\", 4];","title":"Limiting length"},{"location":"expressions/#failure-to-load-at-compile-time-and-defaults","text":"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;","title":"Failure to load at compile time and defaults"},{"location":"faq/","text":"Frequently Asked Questions 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.","title":"FAQ"},{"location":"faq/#frequently-asked-questions","text":"","title":"Frequently Asked Questions"},{"location":"faq/#standard-library","text":"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.","title":"Standard library"},{"location":"faq/#language-features","text":"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;","title":"Language features"},{"location":"faq/#memory-management","text":"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; };","title":"Memory management"},{"location":"faq/#interfacing-with-c-code","text":"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.","title":"Interfacing with C code"},{"location":"faq/#patterns","text":"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) .","title":"Patterns"},{"location":"faq/#syntax-language-design","text":"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.","title":"Syntax & Language design"},{"location":"firstproject/","text":"Your First Project Starting out with C3, you probably want to get a feel for the language, without using the integrated build system. Open a text editor and enter the following in a file you call hello_world.c3 : module hello_world; import std::io; fn int main(String[] argv) { io::printn(\"Hello World!\"); return 0; } Now in the terminal type: $ c3c compile hello_world.c3 $ ./hello_world Hello World $ A real project Once you go beyond simple files, you want to create a real project. Do so by entering c3c init hello_world . You will get the following structure: $ c3c init hello_world $ tree . . \u2514\u2500\u2500 hello_world \u251c\u2500\u2500 project.json \u251c\u2500\u2500 LICENSE \u251c\u2500\u2500 README.md \u251c\u2500\u2500 build \u251c\u2500\u2500 docs \u251c\u2500\u2500 lib \u251c\u2500\u2500 resources \u251c\u2500\u2500 src \u2502 \u2514\u2500\u2500 main.c3 \u2514\u2500\u2500 test Enter main.c3 and write the same code as above, then anywhere in the project structure: $ c3c run Hello World $","title":"Your first project"},{"location":"firstproject/#your-first-project","text":"Starting out with C3, you probably want to get a feel for the language, without using the integrated build system. Open a text editor and enter the following in a file you call hello_world.c3 : module hello_world; import std::io; fn int main(String[] argv) { io::printn(\"Hello World!\"); return 0; } Now in the terminal type: $ c3c compile hello_world.c3 $ ./hello_world Hello World $","title":"Your First Project"},{"location":"firstproject/#a-real-project","text":"Once you go beyond simple files, you want to create a real project. Do so by entering c3c init hello_world . You will get the following structure: $ c3c init hello_world $ tree . . \u2514\u2500\u2500 hello_world \u251c\u2500\u2500 project.json \u251c\u2500\u2500 LICENSE \u251c\u2500\u2500 README.md \u251c\u2500\u2500 build \u251c\u2500\u2500 docs \u251c\u2500\u2500 lib \u251c\u2500\u2500 resources \u251c\u2500\u2500 src \u2502 \u2514\u2500\u2500 main.c3 \u2514\u2500\u2500 test Enter main.c3 and write the same code as above, then anywhere in the project structure: $ c3c run Hello World $","title":"A real project"},{"location":"functions/","text":"Functions 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: single typed explicitly typed any: pass non-any arguments as references implicitly typed any: arguments are implicitly converted to references (use with care) untyped C-style Examples: fn void va_singletyped(int... args) { /* args has type int[] */ } fn void va_variants_explicit(any*... args) { /* args has type any*[] */ } fn void va_variants_implicit(args...) { /* args has type any*[] */ } extern fn void va_untyped(...); // only used for extern C functions fn void test() { va_singletyped(1, 2, 3); int x = 1; any* v = &x; va_variants_explicit(&&1, &x, v); // pass references for non-any arguments va_variants_implicit(1, x, \"foo\"); // arguments are implicitly converted to anys va_untyped(1, x, \"foo\"); // extern C-function } Functions and optional returns The return parameter may be an optional result type \u2013 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 \"); }","title":"Functions"},{"location":"functions/#functions","text":"C3 has both regular functions and member functions. Member functions are functions namespaced using type names, and allows invocations using the dot syntax.","title":"Functions"},{"location":"functions/#regular-functions","text":"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); } }","title":"Regular functions"},{"location":"functions/#function-arguments","text":"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! }","title":"Function arguments"},{"location":"functions/#varargs","text":"There are four types of varargs: single typed explicitly typed any: pass non-any arguments as references implicitly typed any: arguments are implicitly converted to references (use with care) untyped C-style Examples: fn void va_singletyped(int... args) { /* args has type int[] */ } fn void va_variants_explicit(any*... args) { /* args has type any*[] */ } fn void va_variants_implicit(args...) { /* args has type any*[] */ } extern fn void va_untyped(...); // only used for extern C functions fn void test() { va_singletyped(1, 2, 3); int x = 1; any* v = &x; va_variants_explicit(&&1, &x, v); // pass references for non-any arguments va_variants_implicit(1, x, \"foo\"); // arguments are implicitly converted to anys va_untyped(1, x, \"foo\"); // extern C-function }","title":"Varargs"},{"location":"functions/#functions-and-optional-returns","text":"The return parameter may be an optional result type \u2013 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\"); }","title":"Functions and optional returns"},{"location":"functions/#methods","text":"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; } }","title":"Methods"},{"location":"functions/#implicit-first-parameters","text":"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.","title":"Implicit first parameters"},{"location":"functions/#restrictions-on-methods","text":"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.","title":"Restrictions on methods"},{"location":"functions/#contracts","text":"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.","title":"Contracts"},{"location":"functions/#short-function-declaration-syntax","text":"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;","title":"Short function declaration syntax"},{"location":"functions/#lambdas","text":"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); }","title":"Lambdas"},{"location":"functions/#static-initializer-and-finalizers","text":"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.","title":"Static initializer and finalizers"},{"location":"functions/#changing-priority-of-static-initializers-and-finalizers","text":"It is possible to provide an argument to the attributes to set the actual priority. It is recommended that programs use a priority of 1024 or higher. The higher the value, the later it will be called. The lowest priority is 65535. // Print \"Hello World\" at startup. fn void start_world @init(3000) { io::printn(\"World\"); } fn void start_hello @init(2000) { io::print(\"Hello \"); }","title":"Changing priority of static initializers and finalizers"},{"location":"generics/","text":"Generics 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.","title":"Generics"},{"location":"generics/#generics","text":"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.","title":"Generics"},{"location":"ideas/","text":"Ideas All ideas have moved to the issue tracker: https://github.com/c3lang/c3c/issues.","title":"More ideas"},{"location":"ideas/#ideas","text":"All ideas have moved to the issue tracker: https://github.com/c3lang/c3c/issues.","title":"Ideas"},{"location":"libraries/","text":"Libraries Note, the library system is in early alpha, everything below is subject to change C3 allows convenient packaging of C3 source files optionally with statically or dynamically linked libraries. To use such a library, simply pass the path to the library directory and add the library you wish to link to. The compiler will resolve any dependencies to other libraries and only compile those that are in use. How it works A library may be used either packaged or unpacked. If unpacked, it is simply a directory with the .c3l suffix, which contains all the necessary files, if packed, this is simply a compressed variant of a directory with the same structure. The specification In the top of the library resides the manifest.json file which has the following structure: { \"provides\" : \"my_lib\", \"execs\" : [], \"targets\" : { \"macos-x64\" : { \"linkflags\" : [], \"dependencies\" : [], \"linked-libs\" : [\"my_lib_static\", \"Cocoa.framework\", \"c\"] }, \"windows-x64\" : { \"linkflags\" : [\"/stack:65536\"], \"dependencies\" : [\"ms_my_extra\"], \"linked-libs\" : [\"my_lib_static\", \"kernel32\"], \"execs\" : [], } } } In the example here, this library supports two targets: macos-x64 and windows-x64 . If we tried to use it with any other target, the compiler would give an error. We see that if we use the windows-x64 target it will also load the ms_my_extra library. And we also see that the linker would have a special argument on that platform. Both targets expect my_lib_static to be available for linking. If this library provides this or dynamic library it will be in the target sub-directories, so it likely has the path windows-x64/my_lib_static.lib and macos-z64/libmy_lib_static.a . Source code Aside from the manifest, C3 will read any C and C3 files in the same directory as manifest.json as well as any files in the target subdirectory for the current target. For static libraries typically a .c3i file (that is, a C3 file without any implementations) is provided, similar to how .h files are used in C. Additional actions \"exec\" , which is available both at the top level and per-target, lists the scripts which will be invoked when a library is used. This requires running the compiler at full trust level using the --trust=full option. How to \u2013 automatically \u2013 export libraries This is not implemented yet, docs will materialize once it is finished","title":"Libraries"},{"location":"libraries/#libraries","text":"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.","title":"Libraries"},{"location":"libraries/#how-it-works","text":"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.","title":"How it works"},{"location":"libraries/#the-specification","text":"In the top of the library resides the manifest.json file which has the following structure: { \"provides\" : \"my_lib\", \"execs\" : [], \"targets\" : { \"macos-x64\" : { \"linkflags\" : [], \"dependencies\" : [], \"linked-libs\" : [\"my_lib_static\", \"Cocoa.framework\", \"c\"] }, \"windows-x64\" : { \"linkflags\" : [\"/stack:65536\"], \"dependencies\" : [\"ms_my_extra\"], \"linked-libs\" : [\"my_lib_static\", \"kernel32\"], \"execs\" : [], } } } In the example here, this library supports two targets: macos-x64 and windows-x64 . If we tried to use it with any other target, the compiler would give an error. We see that if we use the windows-x64 target it will also load the ms_my_extra library. And we also see that the linker would have a special argument on that platform. Both targets expect my_lib_static to be available for linking. If this library provides this or dynamic library it will be in the target sub-directories, so it likely has the path windows-x64/my_lib_static.lib and macos-z64/libmy_lib_static.a .","title":"The specification"},{"location":"libraries/#source-code","text":"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.","title":"Source code"},{"location":"libraries/#additional-actions","text":"\"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.","title":"Additional actions"},{"location":"libraries/#how-to-automatically-export-libraries","text":"This is not implemented yet, docs will materialize once it is finished","title":"How to \u2013 automatically \u2013 export libraries"},{"location":"macros/","text":"Macros The macro capabilities of C3 reaches across several constructs: macros (prefixed with @ at invocation), generic functions , 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 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","title":"Macros"},{"location":"macros/#macros","text":"The macro capabilities of C3 reaches across several constructs: macros (prefixed with @ at invocation), generic functions , generic modules , compile time variables (prefixed with $ ), macro compile time execution (using $if , $for , $foreach , $switch ) and attributes.","title":"Macros"},{"location":"macros/#a-quick-comparison-of-c-and-c3-macros","text":"","title":"A quick comparison of C and C3 macros"},{"location":"macros/#conditional-compilation","text":"// 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);","title":"Conditional compilation"},{"location":"macros/#macros_1","text":"// 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;","title":"Macros"},{"location":"macros/#dynamic-scoping","text":"// C #define Z() ptr->x->y->z int x = Z(); // C3 ... currently no corresponding functionality ...","title":"Dynamic scoping"},{"location":"macros/#reference-arguments","text":"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);","title":"Reference arguments"},{"location":"macros/#first-class-types","text":"// C #define SIZE(T) (sizeof(T) + sizeof(int)) // C3 macro size($Type) { return $Type.sizeof + int.sizeof; }","title":"First class types"},{"location":"macros/#trailing-blocks-for-macros","text":"// 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; }","title":"Trailing blocks for macros"},{"location":"macros/#first-class-names","text":"// C #define offsetof(T, field) (size_t)(&((T*)0)->field) // C3 macro usz offset($Type, #field) { $Type* t = null; return (usz)(uptr)&t.#field; }","title":"First class names"},{"location":"macros/#declaration-attributes","text":"// 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 { ... }","title":"Declaration attributes"},{"location":"macros/#declaration-macros","text":"// C #define DECLARE_LIST(name) List name = { .head = NULL }; // Use: DECLARE_LIST(hello) // C3 ... currently no corresponding functionality ...","title":"Declaration macros"},{"location":"macros/#stringification","text":"#define CHECK(x) do { if (!x) abort(#x); } while(0) // C3 macro fn check(#expr) { if (!#expr) abort($stringify(#expr)); }","title":"Stringification"},{"location":"macros/#top-level-evaluation","text":"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.","title":"Top level evaluation"},{"location":"macros/#macro-declarations","text":"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; } }","title":"Macro declarations"},{"location":"macros/#macro-methods","text":"Similar to regular methods a macro may also be associated with a particular type: struct Foo { ... } macro Foo.generate(&self) { ... } Foo f; f.generate(); See the chapter on functions for more details.","title":"Macro methods"},{"location":"macros/#capturing-a-trailing-block","text":"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); } } }","title":"Capturing a trailing block"},{"location":"macros/#macros-returning-values","text":"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); }","title":"Macros returning values"},{"location":"macros/#calling-macros","text":"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.","title":"Calling macros"},{"location":"macros/#macro-vaargs","text":"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","title":"Macro vaargs"},{"location":"macros/#vacount","text":"Returns the number of arguments.","title":"$vacount"},{"location":"macros/#vaarg","text":"Returns the argument as a regular parameter. The argument is guaranteed to be evaluated once, even if the argument is used multiple times.","title":"$vaarg"},{"location":"macros/#vaconst","text":"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.","title":"$vaconst"},{"location":"macros/#vaexpr","text":"Returns the argument as an unevaluated expression. Multiple uses will evaluate the expression multiple times, this corresponds to # parameters.","title":"$vaexpr"},{"location":"macros/#vatype","text":"Returns the argument as a type. This corresponds to $Type style parameters, e.g. $vatype(2) a = 2","title":"$vatype"},{"location":"macros/#varef","text":"Returns the argument as an lvalue. This corresponds to &myref style parameters, e.g. $varef(1) = 123 .","title":"$varef"},{"location":"macros/#vasplat","text":"$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 };","title":"$vasplat"},{"location":"macros/#untyped-lists","text":"Compile time variables may hold untyped lists. Such lists may be iterated over or implicitly converted to initializer lists: var $a = { 1, 2 }; $foreach ($x : $a) io::printfn(\"%d\", $x); $endforeach int[2] x = $a; io::printfn(\"%s\", x); io::printfn(\"%s\", $a[1]); // Will print // 1 // 2 // [1, 2] // 2","title":"Untyped lists"},{"location":"modules/","text":"Modules 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: The std::core module (and sub modules). Any other module sharing the same top module. E.g. the module foo::abc will implicitly also import 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\u00b4s 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: Functions, macros, constants and variables require at least the (sub-) module name. Types do not require the module name unless the name is ambiguous. In case of ambiguity, only so many levels of module names are needed as to make the symbol unambiguous. // File a.c3 module a; struct Foo { ... } struct Bar { ... } struct TheAStruct { ... } fn void anAFunction() { ... } // File b.c3 module b; struct Foo { ... } struct Bar { ... } struct TheBStruct { ... } fn void aBFunction() { ... } // File c.c3 module c; import a, b; struct TheCStruct { ... } struct Bar { ... } fn void aCFunction() { ... } fn void test() { TheAStruct stA; TheBStruct stB; TheCStruct stC; // Name required to avoid ambiguity; b::Foo stBFoo; // Will always pick the current module's // name. Bar bar; // Namespace required: a::aAFunction(); b::aBFunction(); // A local symbol does not require it: aCFunction(); } This means that the rule for the common case can be summarized as Types are used without prefix; functions, variables, macros and constants are prefixed with the sub module name. Module sections A single file may have multiple module declarations, even for the same module. This allows us to write for example: // File foo.c3 module foo; fn int hello_world() { return my_hello_world(); } module foo @private; import std::io; // The import is only visible in this section. fn int my_hello_world() // @private by default { io::printn(\"Hello, world\\n\"); return 0; } module foo @test; fn void test_hello() // @test by default { assert(hello_world() == 0); } Versioning and dynamic inclusion NOTE: This feature may significantly change. When including dynamic libraries, it is possible to use optional functions and globals. This is done using the @dynamic attribute. An example library could have this: dynlib.c3i module dynlib; fn void do_something() @dynamic(4.0) fn void do_something_else() @dynamic(0, 5.0) fn void do_another_thing() @dynamic(0, 2.5) Importing the dynamic library and setting the base version to 4.5 and minimum version to 3.0, we get the following: test.c3 import dynlib; fn void test() { if (@available(dynlib::do_something)) { dynlib::do_something(); } else { dynlib::do_someting_else(); } } In this example the code would run do_something if available (that is, when the dynamic library is 4.0 or higher), or fallback to do_something_else otherwise. If we tried to conditionally add something not available in the compilation itself, that is a compile time error: if (@available(dynlib::do_another_thing)) { 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: import std::io; // On Linux or MacOS this will insert 'String a = \"Hello world!\";' $exec(\"echo\", \"String a = \\\\\\\"Hello world!\\\\\\\"\\\\;\"); fn void main() { io::printn(a); } Using $exec requires full trust level , which is enabled with -trust=full from the command line. '$exec' will by default run from the /scripts directory for projects, for non-project builds, the current directory is used as well. $exec scripting $exec allows a special scripting mode, where one or more C3 files are compiled on the fly and run by $exec . import std::io; // Compile foo.c3 and bar.c3 in the /scripts directory, invoke the resulting binary // with the argument 'test' $exec(\"foo.c3;bar.c3\", \"test\"); fn void main() { ... }","title":"Modules"},{"location":"modules/#modules","text":"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 .","title":"Modules"},{"location":"modules/#details","text":"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 .","title":"Details"},{"location":"modules/#importing-modules","text":"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 = {};","title":"Importing modules"},{"location":"modules/#implicit-imports","text":"The module system will also implicitly import: The std::core module (and sub modules). Any other module sharing the same top module. E.g. the module foo::abc will implicitly also import modules foo and foo::cde if they exist.","title":"Implicit imports"},{"location":"modules/#visibility","text":"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 }","title":"Visibility"},{"location":"modules/#overriding-symbol-visibility-rules","text":"By using import @public , it's possible to access another module\u00b4s 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.","title":"Overriding symbol visibility rules"},{"location":"modules/#changing-the-default-visibility","text":"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","title":"Changing the default visibility"},{"location":"modules/#linker-visibility-and-exports","text":"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.","title":"Linker visibility and exports"},{"location":"modules/#using-functions-and-types-from-other-modules","text":"As a rule, functions, macros, constants, variables and types in the same module do not need any namespace prefix. For imported modules the following rules hold: Functions, macros, constants and variables require at least the (sub-) module name. Types do not require the module name unless the name is ambiguous. In case of ambiguity, only so many levels of module names are needed as to make the symbol unambiguous. // File a.c3 module a; struct Foo { ... } struct Bar { ... } struct TheAStruct { ... } fn void anAFunction() { ... } // File b.c3 module b; struct Foo { ... } struct Bar { ... } struct TheBStruct { ... } fn void aBFunction() { ... } // File c.c3 module c; import a, b; struct TheCStruct { ... } struct Bar { ... } fn void aCFunction() { ... } fn void test() { TheAStruct stA; TheBStruct stB; TheCStruct stC; // Name required to avoid ambiguity; b::Foo stBFoo; // Will always pick the current module's // name. Bar bar; // Namespace required: a::aAFunction(); b::aBFunction(); // A local symbol does not require it: aCFunction(); } This means that the rule for the common case can be summarized as Types are used without prefix; functions, variables, macros and constants are prefixed with the sub module name.","title":"Using functions and types from other modules"},{"location":"modules/#module-sections","text":"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); }","title":"Module sections"},{"location":"modules/#versioning-and-dynamic-inclusion","text":"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.","title":"Versioning and dynamic inclusion"},{"location":"modules/#textual-includes","text":"","title":"Textual includes"},{"location":"modules/#include","text":"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).","title":"$include"},{"location":"modules/#exec","text":"An alternative to $include is $exec which is similar to include, but instead includes the output of an external program as the included text. An example: import std::io; // On Linux or MacOS this will insert 'String a = \"Hello world!\";' $exec(\"echo\", \"String a = \\\\\\\"Hello world!\\\\\\\"\\\\;\"); fn void main() { io::printn(a); } Using $exec requires full trust level , which is enabled with -trust=full from the command line. '$exec' will by default run from the /scripts directory for projects, for non-project builds, the current directory is used as well.","title":"$exec"},{"location":"modules/#exec-scripting","text":"$exec allows a special scripting mode, where one or more C3 files are compiled on the fly and run by $exec . import std::io; // Compile foo.c3 and bar.c3 in the /scripts directory, invoke the resulting binary // with the argument 'test' $exec(\"foo.c3;bar.c3\", \"test\"); fn void main() { ... }","title":"$exec scripting"},{"location":"moreexamples/","text":"More Examples Hello World fn void main() { io::printn(\"Hello world!\"); } Fibonacci recursive fn long fib(long n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); } HTTP Server import net::http_server; fn void! httpHandler(HttpContext* context) { context.response.contentType = \"text/plain\"; context.response.printf(\"Hello world!\\n\"); } fn void main() { HttpServer server; server.init(); InetAddress! addr = server.bindPort(8080); if (catch addr) { printf(\"Failed to open server.\\n\"); exit(-1); } printf(\"Begin listening to on http://%s\\n\", addr.description()); server.listen(&httpHandler); }","title":"More Examples"},{"location":"moreexamples/#more-examples","text":"","title":"More Examples"},{"location":"moreexamples/#hello-world","text":"fn void main() { io::printn(\"Hello world!\"); }","title":"Hello World"},{"location":"moreexamples/#fibonacci-recursive","text":"fn long fib(long n) { if (n <= 1) return n; return fib(n - 1) + fib(n - 2); }","title":"Fibonacci recursive"},{"location":"moreexamples/#http-server","text":"import net::http_server; fn void! httpHandler(HttpContext* context) { context.response.contentType = \"text/plain\"; context.response.printf(\"Hello world!\\n\"); } fn void main() { HttpServer server; server.init(); InetAddress! addr = server.bindPort(8080); if (catch addr) { printf(\"Failed to open server.\\n\"); exit(-1); } printf(\"Begin listening to on http://%s\\n\", addr.description()); server.listen(&httpHandler); }","title":"HTTP Server"},{"location":"naming/","text":"Naming rules 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); }","title":"Naming rules"},{"location":"naming/#naming-rules","text":"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.","title":"Naming rules"},{"location":"naming/#structs-unions-enums-and-faults","text":"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 }","title":"Structs, unions, enums and faults"},{"location":"naming/#variables-and-parameters","text":"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++; }","title":"Variables and parameters"},{"location":"naming/#global-constants","text":"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;","title":"Global constants"},{"location":"naming/#enum-fault-values","text":"Enum and fault values follow the same naming standard as global constants. enum Baz { VALUE_1, VALUE_2 } fault Err { OOPS, LOTS_OF_OOPS }","title":"Enum / Fault values"},{"location":"naming/#struct-union-members","text":"Struct and union members follow the same naming rules as variables.","title":"Struct / union members"},{"location":"naming/#modules","text":"Module names may contain a-z, 0-9 and _ , no upper case characters are allowed. module foo;","title":"Modules"},{"location":"naming/#functions-and-macros","text":"Functions and macros must start with a-z after any optional initial _ . fn void theMostAmazingFunction() { return; } macro justDoIt(x) { justDo(x); }","title":"Functions and macros"},{"location":"operators/","text":"Operator overloading 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); } }","title":"Operator overloading"},{"location":"operators/#operator-overloading","text":"C3 allows some limited operator overloading for working with containers.","title":"Operator overloading"},{"location":"operators/#element-at-operator","text":"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.","title":"\"Element at\" operator []"},{"location":"operators/#element-ref-operator","text":"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]; }","title":"\"Element ref\" operator &[]"},{"location":"operators/#element-set-operator","text":"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; }","title":"\"Element set\" operator []="},{"location":"operators/#len-operator","text":"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.","title":"\"len\" operator"},{"location":"operators/#enabling-foreach","text":"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); } }","title":"Enabling 'foreach'"},{"location":"optionals/","text":"Optionals and error handling Optionals in C3 work differently from other languages: It is similar to a \"Result\" type in capabilities. It is not quite a type: it can be used for variable and return values - but not for parameters or struct member types. It \"cascades\" the optional-ness of an expression, somewhat reminiscent of the \"flatmap\" operation. A function called with an Optional is only invoked if it has an actual value. What is an Optional? In C3 it is either: A variable that works as a union between a fault value and an actual value, we call this latter the \"result\". A return value that is either a fault or a result. We can construct an Optional by adding the ! suffix to a type: 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: Evaluate the first argument. If it is a fault then we're done, set x to this fault. Evaluate the second argument. If it is a fault then we're done, set x to this fault. 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: 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.","title":"Optionals and error handling"},{"location":"optionals/#optionals-and-error-handling","text":"Optionals in C3 work differently from other languages: It is similar to a \"Result\" type in capabilities. It is not quite a type: it can be used for variable and return values - but not for parameters or struct member types. It \"cascades\" the optional-ness of an expression, somewhat reminiscent of the \"flatmap\" operation. A function called with an Optional is only invoked if it has an actual value.","title":"Optionals and error handling"},{"location":"optionals/#what-is-an-optional","text":"In C3 it is either: A variable that works as a union between a fault value and an actual value, we call this latter the \"result\". A return value that is either a fault or a result. We can construct an Optional by adding the ! suffix to a type: 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);","title":"What is an Optional?"},{"location":"optionals/#optional-execution","text":"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: Evaluate the first argument. If it is a fault then we're done, set x to this fault. Evaluate the second argument. If it is a fault then we're done, set x to this fault. 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))","title":"Optional execution"},{"location":"optionals/#optional-unwrapping","text":"It's not possible to assign an Optional to a non-optional type: int! a = ... int b = a; // <- ERROR, can't assign \"int!\" to \"int\" To assign it we have two options, if-try and implicit unwrap.","title":"Optional unwrapping"},{"location":"optionals/#if-try","text":"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!","title":"If-try"},{"location":"optionals/#if-catch","text":"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. }","title":"If-catch"},{"location":"optionals/#implicit-unwrapping-with-if-catch","text":"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;","title":"Implicit unwrapping with if-catch."},{"location":"optionals/#getting-the-fault-without-unwrapping","text":"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! }","title":"Getting the fault without unwrapping"},{"location":"optionals/#if-catch-switching","text":"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: ... } }","title":"If-catch switching"},{"location":"optionals/#testing-for-a-result-without-unwrapping","text":"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));","title":"Testing for a result without unwrapping"},{"location":"optionals/#fault-and-anyfault","text":"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","title":"fault and anyfault"},{"location":"optionals/#setting-the-result-and-the-fault","text":"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?","title":"Setting the result and the fault"},{"location":"optionals/#rethrow-or-else-and-force-unwrap","text":"Three helper operators are provided for working with Optionals: rethrow ! , or-else ?? and force unwrap !! .","title":"Rethrow, or-else and force unwrap"},{"location":"optionals/#rethrow","text":"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","title":"Rethrow"},{"location":"optionals/#or-else","text":"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;","title":"Or-else"},{"location":"optionals/#force-unwrap","text":"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()!!;","title":"Force unwrap"},{"location":"optionals/#no-void-variables","text":"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());","title":"No void! variables"},{"location":"optionals/#examples","text":"","title":"Examples"},{"location":"optionals/#basic-usage-with-immediate-error-handling","text":"// 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* }","title":"Basic usage with immediate error handling"},{"location":"optionals/#composability-of-calls","text":"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! } )","title":"Composability of calls"},{"location":"optionals/#returning-a-fault","text":"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?; /* ... */ }","title":"Returning a fault"},{"location":"optionals/#calling-a-function-automatically-returning-any-optional-result","text":"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?; }","title":"Calling a function automatically returning any optional result"},{"location":"optionals/#force-unwrapping-to-panic-on-fault","text":"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\"); }","title":"Force unwrapping to panic on fault"},{"location":"optionals/#catching-faults-to-implicitly-unwrap","text":"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*. }","title":"Catching faults to implicitly unwrap"},{"location":"optionals/#only-run-if-there-is-no-fault","text":"fn void do_something_to_file() { void! res = find_file(); if (try res) { io::printn(\"I found the file\"); } }","title":"Only run if there is no fault"},{"location":"optionals/#catching-and-switch-on-fault","text":"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?; } }","title":"Catching and switch on fault"},{"location":"optionals/#default-values-using-or-else","text":"fn int get_int() { return get_int_number_or_fail() ?? -1; }","title":"Default values using or-else"},{"location":"optionals/#get-the-fault-from-an-optional-without-if-catch","text":"fn void test_catch() { int! i = get_something(); anyfault maybe_fault = @catch(i); if (maybe_fault) { // Do something with the fault } }","title":"Get the fault from an optional without if-catch"},{"location":"optionals/#test-if-something-has-a-value-without-if-try","text":"fn void test_something() { int! i = try_it(); bool worked = @ok(i); if (worked) { io::printn(\"Horray! It worked.\"); } }","title":"Test if something has a value without if-try"},{"location":"optionals/#some-common-techniques","text":"Here follows some common techniques using optional values.","title":"Some common techniques"},{"location":"optionals/#catch-and-return-another-error","text":"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 .. }","title":"Catch and return another error"},{"location":"optionals/#using-void-as-a-boolean","text":"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; } ... }","title":"Using void! as a boolean"},{"location":"optionals/#interfacing-with-c","text":"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.","title":"Interfacing with C"},{"location":"precedence/","text":"Precedence rules Precedence rules in C3 differs from C/C++. Here are all precedence levels in C3, listed from highest (1) to lowest (11): () , [] , . , !! postfix ! , ++ and -- @ , prefix - , ~ , prefix * , & , prefix ++ and -- infix * , / , % , *% << , >> ^ , | , infix & + , infix - == , != , >= , <= , > , < && || ternary ?: ?? = , *= , /= , %= , += , -= , <<= , >>= , &= , ^= , |= The main difference is that bitwise operations and shift has higher precedence than addition/subtraction and multiplication/division in C3. Bitwise operations also have higher precedence than the relational operators. Also, there is no difference in precedence between && || or between the bitwise operators. Examples a + b >> c + d (a + b) >> (c + d) // C (+ - are evaluated before >>) a + (b >> c) + d // C3 (>> is evaluated before + -) a & b == c a & (b == c) // C (bitwise operators are evaluated after relational) (a & b) == c // C3 (bitwise operators are evaluated before relational) a > b == c < d (a > b) == (c < d) // C (< > binds tighter than ==) ((a > b) == c) < d // C3 Error, requires parenthesis! a | b ^ c & d a | ((b ^ c) & d) // C (All bitwise operators have different precedence) ((a | b) ^ c) & d // C3 Error, requires parenthesis! The change in precedence of the bitwise operators corrects a long standing issue in the C specification. The change in precedence for shift operations goes towards making the precedence less surprising. Conflating the precedence of relational and equality operations, and all bitwise operations was motivated by simplification: few remember the exact internal differences in precedence between bitwise operators. Parenthesis are required for those conflated levels of precedence. Left-to-right offers a very simple model to think about the internal order of operations, and encourages use of explicit ordering, as best practice in C is to use parentheses anyway.","title":"Precedence"},{"location":"precedence/#precedence-rules","text":"Precedence rules in C3 differs from C/C++. Here are all precedence levels in C3, listed from highest (1) to lowest (11): () , [] , . , !! postfix ! , ++ and -- @ , prefix - , ~ , prefix * , & , prefix ++ and -- infix * , / , % , *% << , >> ^ , | , infix & + , infix - == , != , >= , <= , > , < && || ternary ?: ?? = , *= , /= , %= , += , -= , <<= , >>= , &= , ^= , |= The main difference is that bitwise operations and shift has higher precedence than addition/subtraction and multiplication/division in C3. Bitwise operations also have higher precedence than the relational operators. Also, there is no difference in precedence between && || or between the bitwise operators. Examples a + b >> c + d (a + b) >> (c + d) // C (+ - are evaluated before >>) a + (b >> c) + d // C3 (>> is evaluated before + -) a & b == c a & (b == c) // C (bitwise operators are evaluated after relational) (a & b) == c // C3 (bitwise operators are evaluated before relational) a > b == c < d (a > b) == (c < d) // C (< > binds tighter than ==) ((a > b) == c) < d // C3 Error, requires parenthesis! a | b ^ c & d a | ((b ^ c) & d) // C (All bitwise operators have different precedence) ((a | b) ^ c) & d // C3 Error, requires parenthesis! The change in precedence of the bitwise operators corrects a long standing issue in the C specification. The change in precedence for shift operations goes towards making the precedence less surprising. Conflating the precedence of relational and equality operations, and all bitwise operations was motivated by simplification: few remember the exact internal differences in precedence between bitwise operators. Parenthesis are required for those conflated levels of precedence. Left-to-right offers a very simple model to think about the internal order of operations, and encourages use of explicit ordering, as best practice in C is to use parentheses anyway.","title":"Precedence rules"},{"location":"primer/","text":"A quick primer on C3 for C programmers This primer is intended as a guide to how the C syntax \u2013 and in some cases C semantics \u2013 is different in C3. It is intended to help you take a piece of C code and understand how it can be converted manually to C3. Struct, enum and union declarations Don't add a ; after enum, struct and union declarations, and note the slightly different syntax for declaring a named struct inside of a struct. // C typedef struct { int a; struct { double x; } bar; } Foo; // C3 struct Foo { int a; struct bar { double x; } } Also, user defined types are used without a struct , union or enum keyword, as if the name was a C typedef. Arrays Array sizes are written next to the type and arrays do not decay to pointers, you need to do it manually: // C int x[2] = { 1, 2 }; int *y = x; // C3 int[2] x = { 1, 2 }; int* y = &x; You will probably prefer slices to pointers when passing data around: // C int x[100] = ...; int y[30] = ...; int z[15] = ...; sortMyArray(x, 100); sortMyArray(y, 30); // Sort part of the array! sortMyArray(z + 1, 10); // C3 int[100] x = ...; int[30] y = ...; sortMyArray(&x); // Implicit conversion from int[100]* -> int[] sortMyArray(&y); // Implicit conversion from int[30]* -> int[] sortMyArray(z[1..10]; // Inclusive ranges! Note that declaring an array of inferred size will look different in C3: // C int x[] = { 1, 2, 3 }; // x is int[3] // C3 int[*] x = { 1, 2, 3 }; // x is int[3] Arrays are trivially copyable: // C int x[3] = ...; int y[3]; for (int i = 0; i < 3; i++) y[i] = x[i]; // C3 int[3] x = ...; int[3] y = x; See more here . Undefined behaviour C3 has less undefined behaviour, in particular integers are defined as using 2s complement and signed overflow is wrapping. See more here . Functions Functions are declared like C, but you need to put fn in front: // C: int foo(Foo *b, int x, void *z) { ... } // C3 fn int foo(Foo* b, int x, void* z) { ... } See more about functions, like named and default arguments here . Calling C functions Declare a function (or variable) with extern and it will be possible to access it from C3: // To access puts: extern fn int puts(char*); ... puts(\"Hello world\"); Note that currently only the C standard library is automatically passed to the linker. In order to link with other libraries, you need to explicitly tell the compiler to link them. If you want to use a different identifier inside of your C3 code compared to the function or variable's external name, use the @extern attribute: extern fn int _puts(char* message) @extern(\"puts\"); ... _puts(\"Hello world\"); // <- calls the puts function in libc Identifiers Name standards are enforced: // Starting with uppercase and followed somewhere by at least // one lower case is a user defined type: Foo x; M____y y; // Starting with lowercase is a variable or a function or a member name: x.myval = 1; int z = 123; fn void fooBar(int x) { ... } // Only upper case is a constant or an enum value: const int FOOBAR = 123; enum Test { STATE_A = 0, STATE_B = 2 } Variable declaration Declaring more than one variable at a time is not allowed: // C int a, b; // Not allowed in C3 // C3 int a; int b; In C3, variables are always zero initialized, unless you explicitly opt out using @noinit : // C int a = 0; int b; // C3 int a; int b @noinit; Compound literals Compound literals use C++ style brace initialization, not cast style like in C. For convenience, assigning to a struct will infer the type even if it's not an initializer. // C Foo f = { 1, 2 }; f = (Foo) { 1, 2 }; callFoo((Foo) { 2, 3 }); // C3 Foo f = { 1, 2 }; f = { 1, 2 }; callFoo(Foo{ 2, 3 }); typedef and #define becomes 'def' typedef is replaced by def : // C typedef Foo* FooPtr; // C3 def FooPtr = Foo*; def also allows you to do things that C uses #define for: // C #define println puts #define my_excellent_string my_string char *my_string = \"Party on\"; ... println(my_excellent_string); // C3 def println = puts; def my_excellent_string = my_string; char* my_string = \"Party on\"; ... println(my_excellent_string); Read more about def here . Basic types Several C types that would be variable sized are fixed size, and others changed names: // C int16_t a; int32_t b; int64_t c; uint64_t d; size_t e; ssize_t f; ptrdiff_t g; intptr_t h; // C3 short a; // Guaranteed 16 bits int b; // Guaranteed 32 bits long c; // Guaranteed 64 bits ulong d; // Guaranteed 64 bits int128 e; // Guaranteed 128 bits uint128 f; // Guaranteed 128 bits usz g; // Same as C size_t, depends on target isz h; // Same as C ptrdiff_t iptr i; // Same as intptr_t depends on target uptr j; // Same as uintptr_t depends on target Read more about types here . Instead of #include: Modules and import Declaring the module name is not mandatory, but if you leave it out the file name will be used as the module name. Imports are recursive. module otherlib::foo; fn void test() { ... } struct FooStruct { ... } module mylib::bar; import otherlib; fn void myCheck() { foo::test(); // foo prefix is mandatory. mylib::foo::test(); // This also works; FooStruct x; // But user defined types don't need the prefix. otherlib::foo::FooStruct y; // But it is allowed. } Comments The /* */ comments are nesting /* This /* will all */ be commented out */ Note that doc comments, starting with /** has special rules for parsing it, and is not considered a regular comment. See contracts for more information. Type qualifiers Qualifiers like const and volatile are removed, but const before a constant will make it treated as a compile time constant. The constant does not need to be typed. const A = false; // Compile time $if A: // This will not be compiled $else // This will be compiled $endif volatile is replaced by macros for volatile load and store. Goto removed goto is removed, but there is labelled break and continue as well as defer to handle the cases when it is commonly used in C. // C Foo *foo = malloc(sizeof(Foo)); if (tryFoo(foo)) goto FAIL; if (modifyFoo(foo)) goto FAIL; free(foo); return true; FAIL: free(foo); return false; // C3, direct translation: do FAIL: { Foo *foo = malloc(sizeof(Foo)); if (tryFoo(foo)) break FAIL; if (modifyFoo(foo)) break FAIL; free(foo); return true; } free(foo); return false; // C3, using defer: Foo *foo = malloc(Foo); defer free(foo); if (tryFoo(foo)) return false; if (modifyFoo(foo)) return false; return true; Changes in switch case statements automatically break. Use nextcase to fallthrough to the next statement, but empty case statements have implicit fallthrough: // C switch (a) { case 1: case 2: doOne(); break; case 3: i = 0; case 4: doFour(); break; case 5: doFive(); default: return false; } // C3 switch (a) { case 1: case 2: doOne(); case 3: i = 0; nextcase; case 4: doFour(); case 5: doFive(); nextcase; default: return false; } Note that we can jump to an arbitrary case using C3: // C switch (a) { case 1: doOne(); goto LABEL3; case 2: doTwo(); break; case 3: LABEL3: doThree(); default: return false; } // C3 switch (a) { case 1: doOne(); nextcase 3; case 2: doTwo(); case 3: doThree(); nextcase; default: return false; } Bitfields are replace by explicit bitstructs A bitstruct has an explicit container type, and each field has an exact bit range. bitstruct Foo : short { int a : 0..2; // Exact bit ranges, bits 0-2 uint b : 3..6; MyEnum c : 7..13; } There exists a simplified form for a bitstruct containing only booleans, it is the same except the ranges are left out: struct Flags : char { bool has_hyperdrive; bool has_tractorbeam; bool has_plasmatorpedoes; } For more information see the section on bitstructs . Other changes The following things are enhancements to C, that does not have a direct counterpart in C. Expression blocks Defer Methods Optionals Semantic macros Generic modules Contracts Compile time evaluation Reflection Operator overloading Macro methods Static initialize and finalize functions Dynamic interfaces For the full list of all new features see the feature list . Finally, the FAQ answers many questions you might have as you start out.","title":"A quick primer on C3"},{"location":"primer/#a-quick-primer-on-c3-for-c-programmers","text":"This primer is intended as a guide to how the C syntax \u2013 and in some cases C semantics \u2013 is different in C3. It is intended to help you take a piece of C code and understand how it can be converted manually to C3.","title":"A quick primer on C3 for C programmers"},{"location":"primer/#struct-enum-and-union-declarations","text":"Don't add a ; after enum, struct and union declarations, and note the slightly different syntax for declaring a named struct inside of a struct. // C typedef struct { int a; struct { double x; } bar; } Foo; // C3 struct Foo { int a; struct bar { double x; } } Also, user defined types are used without a struct , union or enum keyword, as if the name was a C typedef.","title":"Struct, enum and union declarations"},{"location":"primer/#arrays","text":"Array sizes are written next to the type and arrays do not decay to pointers, you need to do it manually: // C int x[2] = { 1, 2 }; int *y = x; // C3 int[2] x = { 1, 2 }; int* y = &x; You will probably prefer slices to pointers when passing data around: // C int x[100] = ...; int y[30] = ...; int z[15] = ...; sortMyArray(x, 100); sortMyArray(y, 30); // Sort part of the array! sortMyArray(z + 1, 10); // C3 int[100] x = ...; int[30] y = ...; sortMyArray(&x); // Implicit conversion from int[100]* -> int[] sortMyArray(&y); // Implicit conversion from int[30]* -> int[] sortMyArray(z[1..10]; // Inclusive ranges! Note that declaring an array of inferred size will look different in C3: // C int x[] = { 1, 2, 3 }; // x is int[3] // C3 int[*] x = { 1, 2, 3 }; // x is int[3] Arrays are trivially copyable: // C int x[3] = ...; int y[3]; for (int i = 0; i < 3; i++) y[i] = x[i]; // C3 int[3] x = ...; int[3] y = x; See more here .","title":"Arrays"},{"location":"primer/#undefined-behaviour","text":"C3 has less undefined behaviour, in particular integers are defined as using 2s complement and signed overflow is wrapping. See more here .","title":"Undefined behaviour"},{"location":"primer/#functions","text":"Functions are declared like C, but you need to put fn in front: // C: int foo(Foo *b, int x, void *z) { ... } // C3 fn int foo(Foo* b, int x, void* z) { ... } See more about functions, like named and default arguments here .","title":"Functions"},{"location":"primer/#calling-c-functions","text":"Declare a function (or variable) with extern and it will be possible to access it from C3: // To access puts: extern fn int puts(char*); ... puts(\"Hello world\"); Note that currently only the C standard library is automatically passed to the linker. In order to link with other libraries, you need to explicitly tell the compiler to link them. If you want to use a different identifier inside of your C3 code compared to the function or variable's external name, use the @extern attribute: extern fn int _puts(char* message) @extern(\"puts\"); ... _puts(\"Hello world\"); // <- calls the puts function in libc","title":"Calling C functions"},{"location":"primer/#identifiers","text":"Name standards are enforced: // Starting with uppercase and followed somewhere by at least // one lower case is a user defined type: Foo x; M____y y; // Starting with lowercase is a variable or a function or a member name: x.myval = 1; int z = 123; fn void fooBar(int x) { ... } // Only upper case is a constant or an enum value: const int FOOBAR = 123; enum Test { STATE_A = 0, STATE_B = 2 }","title":"Identifiers"},{"location":"primer/#variable-declaration","text":"Declaring more than one variable at a time is not allowed: // C int a, b; // Not allowed in C3 // C3 int a; int b; In C3, variables are always zero initialized, unless you explicitly opt out using @noinit : // C int a = 0; int b; // C3 int a; int b @noinit;","title":"Variable declaration"},{"location":"primer/#compound-literals","text":"Compound literals use C++ style brace initialization, not cast style like in C. For convenience, assigning to a struct will infer the type even if it's not an initializer. // C Foo f = { 1, 2 }; f = (Foo) { 1, 2 }; callFoo((Foo) { 2, 3 }); // C3 Foo f = { 1, 2 }; f = { 1, 2 }; callFoo(Foo{ 2, 3 });","title":"Compound literals"},{"location":"primer/#typedef-and-define-becomes-def","text":"typedef is replaced by def : // C typedef Foo* FooPtr; // C3 def FooPtr = Foo*; def also allows you to do things that C uses #define for: // C #define println puts #define my_excellent_string my_string char *my_string = \"Party on\"; ... println(my_excellent_string); // C3 def println = puts; def my_excellent_string = my_string; char* my_string = \"Party on\"; ... println(my_excellent_string); Read more about def here .","title":"typedef and #define becomes 'def'"},{"location":"primer/#basic-types","text":"Several C types that would be variable sized are fixed size, and others changed names: // C int16_t a; int32_t b; int64_t c; uint64_t d; size_t e; ssize_t f; ptrdiff_t g; intptr_t h; // C3 short a; // Guaranteed 16 bits int b; // Guaranteed 32 bits long c; // Guaranteed 64 bits ulong d; // Guaranteed 64 bits int128 e; // Guaranteed 128 bits uint128 f; // Guaranteed 128 bits usz g; // Same as C size_t, depends on target isz h; // Same as C ptrdiff_t iptr i; // Same as intptr_t depends on target uptr j; // Same as uintptr_t depends on target Read more about types here .","title":"Basic types"},{"location":"primer/#instead-of-include-modules-and-import","text":"Declaring the module name is not mandatory, but if you leave it out the file name will be used as the module name. Imports are recursive. module otherlib::foo; fn void test() { ... } struct FooStruct { ... } module mylib::bar; import otherlib; fn void myCheck() { foo::test(); // foo prefix is mandatory. mylib::foo::test(); // This also works; FooStruct x; // But user defined types don't need the prefix. otherlib::foo::FooStruct y; // But it is allowed. }","title":"Instead of #include: Modules and import"},{"location":"primer/#comments","text":"The /* */ comments are nesting /* This /* will all */ be commented out */ Note that doc comments, starting with /** has special rules for parsing it, and is not considered a regular comment. See contracts for more information.","title":"Comments"},{"location":"primer/#type-qualifiers","text":"Qualifiers like const and volatile are removed, but const before a constant will make it treated as a compile time constant. The constant does not need to be typed. const A = false; // Compile time $if A: // This will not be compiled $else // This will be compiled $endif volatile is replaced by macros for volatile load and store.","title":"Type qualifiers"},{"location":"primer/#goto-removed","text":"goto is removed, but there is labelled break and continue as well as defer to handle the cases when it is commonly used in C. // C Foo *foo = malloc(sizeof(Foo)); if (tryFoo(foo)) goto FAIL; if (modifyFoo(foo)) goto FAIL; free(foo); return true; FAIL: free(foo); return false; // C3, direct translation: do FAIL: { Foo *foo = malloc(sizeof(Foo)); if (tryFoo(foo)) break FAIL; if (modifyFoo(foo)) break FAIL; free(foo); return true; } free(foo); return false; // C3, using defer: Foo *foo = malloc(Foo); defer free(foo); if (tryFoo(foo)) return false; if (modifyFoo(foo)) return false; return true;","title":"Goto removed"},{"location":"primer/#changes-in-switch","text":"case statements automatically break. Use nextcase to fallthrough to the next statement, but empty case statements have implicit fallthrough: // C switch (a) { case 1: case 2: doOne(); break; case 3: i = 0; case 4: doFour(); break; case 5: doFive(); default: return false; } // C3 switch (a) { case 1: case 2: doOne(); case 3: i = 0; nextcase; case 4: doFour(); case 5: doFive(); nextcase; default: return false; } Note that we can jump to an arbitrary case using C3: // C switch (a) { case 1: doOne(); goto LABEL3; case 2: doTwo(); break; case 3: LABEL3: doThree(); default: return false; } // C3 switch (a) { case 1: doOne(); nextcase 3; case 2: doTwo(); case 3: doThree(); nextcase; default: return false; }","title":"Changes in switch"},{"location":"primer/#bitfields-are-replace-by-explicit-bitstructs","text":"A bitstruct has an explicit container type, and each field has an exact bit range. bitstruct Foo : short { int a : 0..2; // Exact bit ranges, bits 0-2 uint b : 3..6; MyEnum c : 7..13; } There exists a simplified form for a bitstruct containing only booleans, it is the same except the ranges are left out: struct Flags : char { bool has_hyperdrive; bool has_tractorbeam; bool has_plasmatorpedoes; } For more information see the section on bitstructs .","title":"Bitfields are replace by explicit bitstructs"},{"location":"primer/#other-changes","text":"The following things are enhancements to C, that does not have a direct counterpart in C. Expression blocks Defer Methods Optionals Semantic macros Generic modules Contracts Compile time evaluation Reflection Operator overloading Macro methods Static initialize and finalize functions Dynamic interfaces For the full list of all new features see the feature list . Finally, the FAQ answers many questions you might have as you start out.","title":"Other changes"},{"location":"project/","text":"Customizing A Project A new project is provided with a barebone structure in project.json : { \"version\": \"0.1.0\", \"authors\": [ \"John Doe \" ], \"langrev\": \"1\", \"warnings\": [ \"no-unused\" ], // sources compiled \"sources\": [ \"./**\" ], // directories where C3 library files may be found \"dependency-search-paths\": [ \"lib\" ], // libraries to use \"dependencies\": [], // c compiler \"cc\": \"cc\", // c sources \"c-sources\": [ \"./c-source/**\" ], \"targets\": { \"hello_world\": { \"type\": \"executable\" } } } By default, an executable in assumed, but changing the type to \"static-lib\" or \"dynamic-lib\" creates static library and dynamic library targets respectively. This part will be updated, stay tuned Compilation options The project file contains common settings at the top level, that can be overridden by each target, by simply assigning that particular key. So if the top level defines target to be macos-x64 and the actual target defines it to be windows-x64 , then the windows-x64 will be used for compilation. Similarly, compiler command line parameters can be used in turn to override the target setting. targets The list of targets that can be built. dependencies List of C3 libraries (\".c3l\") to use when compiling the target. sources List of source files to compile. cc C compiler to use for compiling C sources (if C sources are compiled together with C3 files). c-sources List of C sources to compile. version Not handled yet Version for library, will also be provided as a compile time constant. authors Not handled yet List of authors to add for library compilation. langrev Not handled yet The language revision to use. config Not added yet Under the config you define external constants (\"key: value\") that will be included in compilation as if they were global macro constants. export Not added yet Define the list of modules to be exported by a library. Not valid for executables. warnings Not completely supported yet List of warnings to enable during compilation. Target options type This mandatory option should be one of \"executable\", \"dynamic-lib\" and \"static-lib\". More types will be added Using environment variables Not supported yet In addition to constants any values starting with \"$\" will be assumed to be environment variables. For example \"$HOME\" would on unix systems return the home directory. For strings that start with $ but should not be interpreted as an environment variable. For example, the string \"\\$HOME\" would be interpreted as the plain string \"$HOME\"","title":"Project Structure"},{"location":"project/#customizing-a-project","text":"A new project is provided with a barebone structure in project.json : { \"version\": \"0.1.0\", \"authors\": [ \"John Doe \" ], \"langrev\": \"1\", \"warnings\": [ \"no-unused\" ], // sources compiled \"sources\": [ \"./**\" ], // directories where C3 library files may be found \"dependency-search-paths\": [ \"lib\" ], // libraries to use \"dependencies\": [], // c compiler \"cc\": \"cc\", // c sources \"c-sources\": [ \"./c-source/**\" ], \"targets\": { \"hello_world\": { \"type\": \"executable\" } } } By default, an executable in assumed, but changing the type to \"static-lib\" or \"dynamic-lib\" creates static library and dynamic library targets respectively. This part will be updated, stay tuned","title":"Customizing A Project"},{"location":"project/#compilation-options","text":"The project file contains common settings at the top level, that can be overridden by each target, by simply assigning that particular key. So if the top level defines target to be macos-x64 and the actual target defines it to be windows-x64 , then the windows-x64 will be used for compilation. Similarly, compiler command line parameters can be used in turn to override the target setting.","title":"Compilation options"},{"location":"project/#targets","text":"The list of targets that can be built.","title":"targets"},{"location":"project/#dependencies","text":"List of C3 libraries (\".c3l\") to use when compiling the target.","title":"dependencies"},{"location":"project/#sources","text":"List of source files to compile.","title":"sources"},{"location":"project/#cc","text":"C compiler to use for compiling C sources (if C sources are compiled together with C3 files).","title":"cc"},{"location":"project/#c-sources","text":"List of C sources to compile.","title":"c-sources"},{"location":"project/#version","text":"Not handled yet Version for library, will also be provided as a compile time constant.","title":"version"},{"location":"project/#authors","text":"Not handled yet List of authors to add for library compilation.","title":"authors"},{"location":"project/#langrev","text":"Not handled yet The language revision to use.","title":"langrev"},{"location":"project/#config","text":"Not added yet Under the config you define external constants (\"key: value\") that will be included in compilation as if they were global macro constants.","title":"config"},{"location":"project/#export","text":"Not added yet Define the list of modules to be exported by a library. Not valid for executables.","title":"export"},{"location":"project/#warnings","text":"Not completely supported yet List of warnings to enable during compilation.","title":"warnings"},{"location":"project/#target-options","text":"","title":"Target options"},{"location":"project/#type","text":"This mandatory option should be one of \"executable\", \"dynamic-lib\" and \"static-lib\". More types will be added","title":"type"},{"location":"project/#using-environment-variables","text":"Not supported yet In addition to constants any values starting with \"$\" will be assumed to be environment variables. For example \"$HOME\" would on unix systems return the home directory. For strings that start with $ but should not be interpreted as an environment variable. For example, the string \"\\$HOME\" would be interpreted as the plain string \"$HOME\"","title":"Using environment variables"},{"location":"reflection/","text":"Reflection 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;","title":"Reflection"},{"location":"reflection/#reflection","text":"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.","title":"Reflection"},{"location":"reflection/#compile-time-reflection","text":"During compile time there are a number of compile time fields that may be accessed directly.","title":"Compile time reflection"},{"location":"reflection/#type-properties","text":"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","title":"Type properties"},{"location":"reflection/#alignof","text":"Returns the alignment in bytes needed for the type. struct Foo @align(8) { int a; } uint a = Foo.alignof; // 8","title":"alignof"},{"location":"reflection/#associated","text":"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\"","title":"associated"},{"location":"reflection/#elements","text":"Returns the element count of an enum or fault. enum FooEnum { BAR, BAZ } int x = FooEnum.elements; // 2","title":"elements"},{"location":"reflection/#inf","text":"Only available for floating point types Returns a representation of floating point \"infinity\".","title":"inf"},{"location":"reflection/#inner","text":"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.","title":"inner"},{"location":"reflection/#kindof","text":"Returns the underlying TypeKind as defined in std::core::types. TypeKind kind = int.kindof; // TypeKind.SIGNED_INT","title":"kindof"},{"location":"reflection/#len","text":"Returns the length of the array. usz len = int[4].len; // 4","title":"len"},{"location":"reflection/#max","text":"Returns the maximum value of the type (only valid for integer and float types). ushort max_ushort = ushort.max; // 65535","title":"max"},{"location":"reflection/#membersof","text":"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 .","title":"membersof"},{"location":"reflection/#min","text":"Returns the minimum value of the type (only valid for integer and float types). ichar min_ichar = ichar.min; // -128","title":"min"},{"location":"reflection/#names","text":"Returns a subarray containing the names of an enum or fault. enum FooEnum { BAR, BAZ } String[] x = FooEnum.names; // [\"BAR\", \"BAZ\"]","title":"names"},{"location":"reflection/#params","text":"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\"","title":"params"},{"location":"reflection/#parentof","text":"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\"","title":"parentof"},{"location":"reflection/#returns","text":"Only available for function types. Returns the typeid of the return type. def TestFunc = fn int(int, double); String s = TestFunc.returns.nameof; // \"int\"","title":"returns"},{"location":"reflection/#sizeof","text":"Returns the size in bytes for the given type, like C sizeof . usz x = Foo.sizeof;","title":"sizeof"},{"location":"reflection/#typeid","text":"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;","title":"typeid"},{"location":"reflection/#values","text":"Returns a subarray containing the values of an enum or fault. enum FooEnum { BAR, BAZ } String x = FooEnum.values[1].nameof; // \"BAR\"","title":"values"},{"location":"reflection/#compile-time-functions","text":"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","title":"Compile time functions"},{"location":"reflection/#alignof_1","text":"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","title":"$alignof"},{"location":"reflection/#defined","text":"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","title":"$defined"},{"location":"reflection/#eval","text":"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 }","title":"$eval"},{"location":"reflection/#evaltype","text":"Similar to $eval but for types: $evaltype(\"float\") f = 12.0f;","title":"$evaltype"},{"location":"reflection/#extnameof","text":"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\"","title":"$extnameof"},{"location":"reflection/#nameof","text":"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\"","title":"$nameof"},{"location":"reflection/#offsetof","text":"Returns the offset of a member in a struct. Foo z; $offsetof(z.y); // => returns 8 on 64 bit, 4 on 32 bit","title":"$offsetof"},{"location":"reflection/#qnameof","text":"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\"","title":"$qnameof"},{"location":"reflection/#sizeof_1","text":"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;","title":"$sizeof"},{"location":"reflection/#stringify","text":"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\"","title":"$stringify"},{"location":"reflection/#typeof","text":"Returns the type of an expression or variable as a type itself. Foo f; $typeof(f) x = f;","title":"$typeof"},{"location":"rejectedideas/","text":"Rejected ideas These are ideas that will not be implemented in C3 with rationale given. Constructors and destructors A fundamental concept in C3 is that data is not \"active\". This is to say there is no code associated with the data implicitly unlike constructors and destructors in an object oriented language. Not having constructors / destructors prevents RAII-style resource handling, but also allows the code to assume the memory can be freely allocated and initialized as it sees fit, without causing any corruption or undefined behaviour. There is a fundamental difference between active objects and inert data, each has its advantages and disadvantages. C3 follows the C model, which is that data is passive and does not enforce any behaviour. This has very deep implications on the semantics of the language and adding constructors and destructors would change the language greatly, requiring modification of many parts of the language altering. For that reason constructors and destructors will not be considered for C3. Unicode identifiers The main argument for unicode identifiers is that \"it allows people to code in their own language\". However, there is no proof that this actually is used in practice. Furthermore there are practical issues, such as bidirectional text, characters with different code points that are rendered in an identical way etc. Given the complexity and the lack of actual proven benefit, unicode identifiers will not happen for C3.","title":"Rejected ideas"},{"location":"rejectedideas/#rejected-ideas","text":"These are ideas that will not be implemented in C3 with rationale given.","title":"Rejected ideas"},{"location":"rejectedideas/#constructors-and-destructors","text":"A fundamental concept in C3 is that data is not \"active\". This is to say there is no code associated with the data implicitly unlike constructors and destructors in an object oriented language. Not having constructors / destructors prevents RAII-style resource handling, but also allows the code to assume the memory can be freely allocated and initialized as it sees fit, without causing any corruption or undefined behaviour. There is a fundamental difference between active objects and inert data, each has its advantages and disadvantages. C3 follows the C model, which is that data is passive and does not enforce any behaviour. This has very deep implications on the semantics of the language and adding constructors and destructors would change the language greatly, requiring modification of many parts of the language altering. For that reason constructors and destructors will not be considered for C3.","title":"Constructors and destructors"},{"location":"rejectedideas/#unicode-identifiers","text":"The main argument for unicode identifiers is that \"it allows people to code in their own language\". However, there is no proof that this actually is used in practice. Furthermore there are practical issues, such as bidirectional text, characters with different code points that are rendered in an identical way etc. Given the complexity and the lack of actual proven benefit, unicode identifiers will not happen for C3.","title":"Unicode identifiers"},{"location":"sample/","text":"Sample 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; }","title":"More code examples"},{"location":"sample/#sample","text":"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; }","title":"Sample"},{"location":"setup/","text":"Setup C3 has precompiled binaries for Windows, MacOS and Ubuntu. For other platforms it should be possible to compile it on any platform LLVM can compile to. You will need CMake installed. 1. Install LLVM See LLVM the LLVM documentation on how to set up LLVM for development. On MacOS, installing through Homebrew works fine. Using apt-get on Linux should work fine as well. For Windows you can download suitable pre-compiled LLVM binaries from https://github.com/c3lang/win-llvm 2. Clone the C3 compiler source code from Github This should be as simple as doing: git clone https://github.com/c3lang/c3c.git ... from the command line. 3. Build the compiler Create the build directory: MyMachine:c3c$ mkdir build MyMachine:c3c$ cd build/ Use CMake to set up: MyMachine:c3c/build$ cmake ../ Build the compiler: MyMachine:c3c/build$ make 4. Test it out MyMachine:c3c/build$ ./c3c compile-run ../resources/testfragments/helloworld.c3","title":"Setup"},{"location":"setup/#setup","text":"C3 has precompiled binaries for Windows, MacOS and Ubuntu. For other platforms it should be possible to compile it on any platform LLVM can compile to. You will need CMake installed.","title":"Setup"},{"location":"setup/#1-install-llvm","text":"See LLVM the LLVM documentation on how to set up LLVM for development. On MacOS, installing through Homebrew works fine. Using apt-get on Linux should work fine as well. For Windows you can download suitable pre-compiled LLVM binaries from https://github.com/c3lang/win-llvm","title":"1. Install LLVM"},{"location":"setup/#2-clone-the-c3-compiler-source-code-from-github","text":"This should be as simple as doing: git clone https://github.com/c3lang/c3c.git ... from the command line.","title":"2. Clone the C3 compiler source code from Github"},{"location":"setup/#3-build-the-compiler","text":"Create the build directory: MyMachine:c3c$ mkdir build MyMachine:c3c$ cd build/ Use CMake to set up: MyMachine:c3c/build$ cmake ../ Build the compiler: MyMachine:c3c/build$ make","title":"3. Build the compiler"},{"location":"setup/#4-test-it-out","text":"MyMachine:c3c/build$ ./c3c compile-run ../resources/testfragments/helloworld.c3","title":"4. Test it out"},{"location":"specification/","text":"Specification 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: White space Comment Doc Comment Token A token may be: Identifier Keyword Literal Separator Operator A Doc Comment consists of: A stream of descriptive text A list of directive Tokens Those input elements that are not white space or comments are tokens. The tokens are the terminal symbols of the syntactic grammar. Whitespace and comments can serve to separate tokens that might be tokenized in another manner. For example the characters + and = may form the operator token += only if there is no intervening white space or comment. White Space White space is defined as the ASCII horizontal tab character (U+0009), form feed character (U+000A), vertical tab ( U+000B), carriage return (U+000D), space character (U+0020) and the line terminator character (U+000D). WHITESPACE ::= [ \\t\\f\\v\\r\\n] Letters and digits UC_LETTER ::= [A-Z] LC_LETTER ::= [a-z] LETTER ::= UC_LETTER | LC_LETTER DIGIT ::= [0-9] HEX_DIGIT ::= [0-9a-fA-F] BINARY_DIGIT ::= [01] OCTAL_DIGIT ::= [0-7] LC_LETTER_US ::= LC_LETTER | \"_\" UC_LETTER_US ::= UC_LETTER | \"_\" ALPHANUM ::= LETTER | DIGIT ALPHANUM_US ::= ALPHANUM | \"_\" UC_ALPHANUM_US ::= UC_LETTER_US | DIGIT LC_ALPHANUM_US ::= LC_LETTER_US | DIGIT Comments There are three types of regular comments: // text a line comment. The text between // and line end is ignored. /* text */ block comments. The text between /* and */ is ignored. It has nesting behaviour, so for every /* discovered between the first /* and the last */ a corresponding */ must be found. Doc comments /** 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: \\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 , \\u00FF , \\U000000FF and \\xc3\\xbf represent the two bytes 0xc3 0xbf of the UTF-8 encoding of character U+00FF . STRING_LIT ::= \\x22 (CHAR_LIT_BYTE | UNICODE_CHAR)* \\x22 Compile time string concatenation Strings will concatenate if declared in sequence. Example: String s = \"abc\" \"def\" \"ghi\"; // This is equivalent to: String s = \"abcdefghi\"; Raw string literals Raw string literals are enclosed between `` and consist of the raw UTF8 in the source code between the \"`\". A sequence of two \"`\" will be interpreted as a single escaped \"`\" that does not terminate the literal. Compile time concatenation Raw strings will concatenate with other regular strings and raw strings ( see string literal compile time concatenation ). Source code pre-filtering The source code will pre-filter \\r ( 0x0D ) from the source code. This means that it is also implicitly filtered out of raw strings. Character literals A character literal is enclosed in ' and may either consist of 1, 2, 4, 8, 16 bytes. CHARACTER_LIT ::= \"'\" (CHAR_LIT_BYTE+) | UNICODE_CHAR \"'\" Types Types consist of built-in types and user-defined types (enums, structs, unions, bitstructs, fault and distinct). Boolean types bool may have the two values true and false . It holds a single bit of information but is stored in a char type. Integer types The built-in integer types: char unsigned 8-bit ichar signed 8-bit ushort unsigned 16-bit short signed 16-bit uint unsigned 32-bit int signed 32-bit ulong unsigned 64-bit long signed 64-bit uint128 unsigned 128-bit int128 singed 128-bit In addition, the following type aliases exist: uptr unsigned pointer size iptr signed pointer size usz unsigned pointer offset / object size isz signed pointer offset / object size Floating point types Built-in floating point types: float16 IEEE 16-bit* bfloat16 Brainfloat* float IEEE 32-bit double IEEE 64-bit float128 IEEE 128-bit* (* optionally supported) Vector types A vector lowers to the platform's vector types where available. A vector has a base type and a width. vector_type ::= type \"[<\" length \">]\" Vector base type The base type of a vector must be boolean, an integer or a floating point type. Min width The vector width must be at least 1. Element access Vector elements are accessed using [] . It is possible to take the address of a single element. Alignment Alignment of vectors are platform dependent, but is at least the alignment of its element type. Vector operations Vectors support the same arithmetics as its underlying type, and will perform the operation element-wise. Example: int[<2>] a = { 1, 3 }; int[<2>] b = { 2, 7 }; int[<2>] c = a * b; // Equivalent to int[<2>] c = { a[0] * b[0], a[1] * b[1] }; Array types An array has the alignment of its elements. An array must have at least one element. 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. pointer_type ::= type \"*\" Pointee type The type of the memory pointed to is the pointee type . It may be any runtime type. iptr and uptr A pointer may be losslessly cast to an iptr or uptr . An iptr or uptr may be cast to a pointer of any type. The wildcard pointer void* The void* may implicitly cast into any other pointer type. The void* [implicitly casts into any other pointer. A void* pointer may never be dereferenced. Pointer arithmetic on void* Performing pointer arithmetics on void* will assume that the element size is 1. This includes pointer arithmetics using subscripting. Subscripting Subscripting a pointer is equal to performing pointer arithmetics using the index, followed by a deref. Subscripts on pointers may be negative and will never do bounds checks. Deref Dereferencing a pointer will return the value in the memory location interpreted as the pointee type . Struct types A struct may not have zero members. Alignment A non-packed struct has the alignment of the member that has the highest alignment. A packed struct has alignment 1. See align attribute for details on changing the alignment. Union types A union may not have zero members. Alignment A union has the alignment of the member that has the highest alignment. See align attribute for details on changing the alignment. Fault types A fault is an extensible enum which can be used to create an optional . Alignment A fault type has the same alignment as a pointer. See align attribute for details on changing the alignment. Enum types Function types Typeid type The typeid is a pointer sized value which uniquely identifies a type. Any* type The any* is a fat pointer (2 pointers wide) holding a pointer to a value and its corresponding typeid . It cannot be dereferenced. Fields .ptr returns a void* pointer to the underlying value .type returns the typeid of the underlying value. Switching over any Switching over an any value creates an any switch . Anyfault type Declarations and scope Expressions Assignment expression assignment_expr ::= ct_type_assign | unary_expr assignment_op expr ct_type_assign ::= ct_type_ident \"=\" type assignment_op ::= \"=\" | \"+=\" | \"-=\" | \"*=\" | \"/=\" | \"%=\" | \"<<=\" | \">>=\" | \"&=\" | \"^=\" | \"|=\" Type assign This assigns a new type to a compile time type variable. The value of the expression is the type assigned. Combined assign All assignment operations except for \"=\" are combined assign operation. They first perform the operation indicated by the leftmost character(s) in the operator (e.g + for += , << for <<= etc) with the lhs and the rhs. The result is then assigned to the left hand side. The result of the operation is the new value of the left hand side. Implicit conversion If the left hand side is a pointer and the operation is \"+=\" or \"-=\" an attempt to implicitly convert to isz/usz will be tried. For all other types and operations, an implicit conversion of rhs to the type of lhs will be tried. Ternary, elvis and or-else expressions ternary_group_expr ::= suffix_group_expr | ternary_expr | elvis_expr | orelse_expr ternary_expr ::= or_expr \"?\" expr \":\" ternary_group_expr elvis_expr ::= suffix_expr \"?:\" ternary_group_expr orelse_expr ::= suffix_expr \"??\" ternary_group_expr Ternary evaluation The most left-hand expression is evaluated to a boolean. If it is true, the value of the middle expression is returned, otherwise the last expression is returned. Only the most left-hand expression and the returned expressions are evaluated. The middle and last expression are implicitly converted to their unified type . The resulting type is the unified type . Elvis evaluation Lhs and rhs are implicitly converted to their unified type . The lhs is evaluated, it is then converted to a boolean, if the result it true, return the lhs value before its boolean conversion. Otherwise return the right hand side. The right hand side is only evaluated if the lhs evaluates to false. The resulting type is the unified type . Orelse evaluation The lhs must be optional. The non-optional type for lhs and rhs are calculated. The unified type of the result is calculated. Lhs are converted to the unified type preserving their optionality. At runtime, lhs is evaluated. If it evaluates to an optional, rhs is returned instead. Rhs is only evaluated if lhs evaluates to an optional. The resulting type of the orelse is the post conversion type of the rhs. Suffix expression Suffix expressions convert a fault to an optional. suffix_group_exp ::= or_group_expr | suffix_expr suffix_expr ::= or_group_expr \"?\" \"!\"? Effect of \"?\" The \"?\" will convert the expression into an optional. The left hand side must be a fault type. If an optional \"!\" follows, this optional is immediately returned, as if by a return ? 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: void* a = &a; // Valid int a = a + 1; // Invalid Defer statement The defer statements are executed at (runtime) scope exit, whether through return , break , continue or rethrow. defer_stmt ::= \"defer\" (\"try\" | \"catch\")? stmt Defer in defer The defer body (statement) may not be a defer statement. However, if the body is a compound statement then this may have any number of defer statements. Static and tlocal variables in defer Static and tlocal variables are allowed in a defer statement. Only a single variable is instantiated regardless of the number of inlining locations. Defer and return If the return has an expression, then it is evaluated before the defer statements (due to exit from the current function scope), are executed. Example: int a = 0; defer a++; return a; // This is equivalent to int a = 0; int temp = a; a++; return temp; Defer and jump statements A defer body may not contain a break , continue , return or rethrow that would exit the statement. Defer execution Defer statements are executed in the reverse order of their declaration, starting from the last declared defer statement. Defer try A defer try type of defer will only execute if the scope is left through normal fallthrough, break , continue or a return with a result. It will not execute if the exit is through a rethrow or a return with an optional value. Defer catch A defer catch type of defer will only execute if the scope is left through a rethrow or a return with an optional value It will not execute if the exit is a normal fallthrough, break , continue or a return with a result. Non-regular returns - longjmp, panic and other errors Defers will not execute when doing longjmp terminating through a panic or other error. They are only invoked on regular scope exits. Expr statement An expression statement evaluates an expression. expr_stmt ::= expr \";\" No discard If the expression is a function or macro call either returning an optional or annotated @nodiscard , then the expression is a compile time error. A function or macro returning an optional can use the @maydiscard attribute to suppress this error. If statement An if statement will evaluate the cond expression, then execute the first statement (the \"then clause\") in the if-body if it evaluates to \"true\", otherwise execute the else clause. If no else clause exists, then the next statement is executed. if_stmt ::= \"if\" (label \":\")? \"(\" cond_expr \")\" if_body if_body ::= non_compound_stmt | compound_stmt else_clause? | \"{\" switch_body \"}\" else_clause ::= \"else\" (if_stmt | compound_stmt) Scopes Both the \"then\" clause and the else clause open new scopes, even if they are non-compound statements. The cond expression scope is valid until the exit of the entire statement, so any declarations in the cond expression are available both in then and else clauses. Declarations in the \"then\" clause is not available in the else clause and vice versa. Special parsing of the \"then\" clause If the then-clause isn't a compound statement, then it must follow on the same row as the cond expression. It may not appear on a consecutive row. Break It is possible to use labelled break to break out of an if statement. Note that an unlabelled break may not be used. If-try The cond expression may be a try-unwrap chain. In this case, the unwrapped variables are scoped to the \"then\" clause only. If-catch The cond expression may be a catch-unwrap. The unwrap is scoped to the \"then\" clause only. If one or more variables are in the catch, then the \"else\" clause have these variables implicitly unwrapped. Example: int! a = foo(); int! b = foo(); if (catch a, b) { // Do something } else { int x = a + b; // Valid, a and b are implicitly unwrapped. } If-catch implicit unwrap If an if-catch's \"then\"-clause will jump out of the outer scope in all code paths and the catch is on one or more variables, then this variable(s) will be implicitly unwrapped in the outer scope after the if-statement. Example: int! a = foo(); if (catch a) { return; } int x = a; // Valid, a is implicitly unwrapped. Nextcase statement Nextcase will jump to another switch case. nextcase_stmt ::= \"nextcase\" ((label \":\")? (expr | \"default\"))? \";\" Labels When a nextcase has a label, the jump is to the switch in an outer scope with the corresponding label. No expression jumps A nextcase without any expression jumps to the next case clause in the current switch. It is not possible to use no expression nextcase with labels. Jumps to default Using default jumps to the default clause of a switch. Missing case If the switch has constant case values, and the nextcase expression is constant, then the value of the expression must match a case clause. Not matching a case is a compile time error. If one or more cases are non-constant and/or the nextcase expression is non-constant, then no compile time check is made. Variable expression If the nextcase has a non-constant expression, or the cases are not all constant, then first the nextcase expression is evaluated. Next, execution will proceed as if the switch was invoked again, but with the nextcase expression as the switch cond expression. See switch statement . If the switch does not have a cond expression, nextcase with an expression is not allowed. Unreachable code Any statement in the same scope after a nextcase are considered unreachable . Switch statement switch_stmt ::= \"switch\" (label \":\")? (\"(\" cond_expr \")\")? switch body switch_body ::= \"{\" case_clause* \"}\" case_clause ::= default_stmt | case_stmt default_stmt ::= \"default\" \":\" stmt* case_stmt ::= \"case\" label? expr (\"..\" expr)? \":\" stmt* Regular switch If the cond expression exists and all case statements have constant expression, then first the cond expression is evaluated, next the case corresponding to the expression's value will be jumped to and the statement will be executed. After reaching the end of the statements and a new case clause or the end of the switch body, the execution will jump to the first statement after the switch. If-switch If the cond expression is missing or the case statements are non-constant expressions, then each case clause will be evaluated in order after the cond expression has been evaluated (if it exists): If a cond expression exists, calculate the case expression and execute the case if it is matching the cond expression. A default statement has no expression and will always be considered matching the cond expression reached. If no con expression exists, calculate the case expression and execute the case if the expression evaluates to \"true\" when implicitly converted to boolean. A default statement will always be considered having the \"true\" result. Any-switch If the cond expression is an any* type, the switch is handled as if switching was done over the type field of the any* . This field has the type of typeid , and the cases follows the rules for switching over typeid . If the cond expression is a variable, then this variable is implicitly converted to a pointer with the pointee type given by the case statement. Example: any* a = abc(); switch (a) { case int: int b = *a; // a is int* case float: float z = *a; // a is float* case Bar: Bar f = *a; // a is Bar* default: // a is not unwrapped } Ranged cases Cases may be ranged. The start and end of the range must both be constant integer values. The start must be less or equal to the end value. Using non-integers or non-constant values is a compile time error. Fallthrough If a case clause has no statements, then when executing the case, rather than exiting the switch, the next case clause immediately following it will be executed. If that one should also be missing statement, the procedure will be repeated until a case clause with statements is encountered (and executed), or the end of the switch is reached. Exhaustive switch If a switch case has a default clause or it is switching over an enum and there exists a case for each enum value then the switch is exhaustive. Break If an unlabelled break, or a break with the switch's label is encountered, then the execution will jump out of the switch and proceed directly after the end of the switch body. Unreachable code If a switch is exhaustive and all case clauses end with a jump instruction, containing no break statement out of the current switch, then the code directly following the switch will be considered unreachable . Switching over typeid If the switch cond expression is a typeid, then case declarations may use only the type name after the case, which will be interpreted as having an implicit .typeid . Example: case int: will be interpreted as if written case int.typeid . Nextcase without expression Without a value nextcase will jump to the beginning of the next case clause. It is not allowed to put nextcase without an expression if there are no following case clauses. Nextcase with expression Nextcase with an expression will evaluate the expression and then jump as if the switch was entered with the cond expression corresponding to the value of the nextcase expression. Nextcase with an expression cannot be used on a switch without a cond expression. Do statement The do statement first evaluates its body (inner statement), then evaluates the cond expression. If the cond expression evaluates to true, jumps back into the body and repeats the process. do_stmt ::= \"do\" label? compound_stmt (\"while\" \"(\" cond_expr \")\")? \";\" Unreachable code The statement after a do is considered unreachable if the cond expression cannot ever be false and there is no break out of the do. Break break will exit the do with execution continuing on the following statement. Continue continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached. Do block If no while part exists, it will only execute the block once, as if it ended with while (false) , this is called a \"do block\" For statement The for statement will perform the (optional) init expression. The cond expression will then be tested. If it evaluates to true then the body will execute, followed by the incr expression. After execution will jump back to the cond expression and execution will repeat until the cond expression evaluates to false . for_stmt ::= \"for\" label? \"(\" init_expr \";\" cond_expr? \";\" incr_expr \")\" stmt init_expr ::= decl_expr_list? incr_expr ::= expr_list? Init expression The init expression is only executed once before the rest of the for loop is executed. Any declarations in the init expression will be in scope until the for loop exits. The init expression may optionally be omitted. Incr expression The incr expression is evaluated before evaluating the cond expr every time except for the first one. The incr expression may optionally be omitted. Cond expression The cond expression is evaluated every loop. Any declaration in the cond expression is scoped to the current loop, i.e. it will be reinitialized at the start of every loop. The cond expression may optionally be omitted. This is equivalent to setting the cond expression to always return true . Unreachable code The statement after a for is considered unreachable if the cond expression cannot ever be false, or is omitted and there is no break out of the loop. Break break will exit the for with execution continuing on the following statement after the for . Continue continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached. Equivalence of while and for A while loop is functionally equivalent to a for loop without init and incr expressions. Foreach and foreach_r statements The foreach statement will loop over a sequence of values. The foreach_r is equivalent to foreach but the order of traversal is reversed. foreach starts with element 0 and proceeds step by step to element len - 1 . foreach_r starts starts with element len - 1 and proceeds step by step to element 0 . foreach_stmt ::= \"foreach\" label? \"(\" foreach_vars \":\" expr \")\" stmt foreach_r_stmt ::= \"foreach_r\" label? \"(\" foreach_vars \":\" expr \")\" stmt foreach_vars ::= (foreach_index \",\")? foreach_var foreach_var ::= type? \"&\"? IDENTIFIER Break break will exit the foreach statement with execution continuing on the following statement after. Continue continue will cause the next iteration to commence, as if the end of the statement had been reached. Iteration by value or reference Normally iteration are by value. Each element is copied into the foreach variable. If & is added before the variable name, the elements will be retrieved by reference instead, and consequently the type of the variable will be a pointer to the element type instead. Foreach variable The foreach variable may omit the type. In this case the type is inferred. If the type differs from the element type, then an implicit conversion will be attempted. Failing this is a compile time error. Foreach index If a variable name is added before the foreach variable, then this variable will receive the index of the element. For foreach_r this mean that the first value of the index will be len - 1 . The index type defaults to usz . If an optional type is added to the index, the index will be converted to this type. The type must be an integer type. The conversion happens as if the conversion was a direct cast. If the actual index value would exceed the maximum representable value of the type, this does not affect the actual iteration, but may cause the index value to take on an incorrect value due to the cast. For example, if the optional index type is char and the actual index is 256 , then the index value would show 0 as (char)256 evaluates to zero. Modifying the index variable will not affect the foreach iteration. Foreach support Foreach is natively supported for any 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.","title":"Specification"},{"location":"specification/#specification","text":"THIS SPECIFICATION IS UNDER DEVELOPMENT","title":"Specification"},{"location":"specification/#notation","text":"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.","title":"Notation"},{"location":"specification/#source-code-representation","text":"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.","title":"Source code representation"},{"location":"specification/#lexical-translations","text":"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.","title":"Lexical Translations"},{"location":"specification/#line-terminators","text":"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.","title":"Line Terminators"},{"location":"specification/#input-elements-and-tokens","text":"An input element may be: White space Comment Doc Comment Token A token may be: Identifier Keyword Literal Separator Operator A Doc Comment consists of: A stream of descriptive text A list of directive Tokens Those input elements that are not white space or comments are tokens. The tokens are the terminal symbols of the syntactic grammar. Whitespace and comments can serve to separate tokens that might be tokenized in another manner. For example the characters + and = may form the operator token += only if there is no intervening white space or comment.","title":"Input Elements and Tokens"},{"location":"specification/#white-space","text":"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]","title":"White Space"},{"location":"specification/#letters-and-digits","text":"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","title":"Letters and digits"},{"location":"specification/#comments","text":"There are three types of regular comments: // text a line comment. The text between // and line end is ignored. /* text */ block comments. The text between /* and */ is ignored. It has nesting behaviour, so for every /* discovered between the first /* and the last */ a corresponding */ must be found.","title":"Comments"},{"location":"specification/#doc-comments","text":"/** 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.","title":"Doc comments"},{"location":"specification/#identifiers","text":"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*","title":"Identifiers"},{"location":"specification/#keywords","text":"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","title":"Keywords"},{"location":"specification/#operators-and-punctuation","text":"The following character sequences represent operators and punctuation. & @ ~ | ^ : , / $ . ; ) > < # { } - ( ) * [ ] % >= <= + += -= ! ? ?: && ?? &= |= ^= /= .. == ({ }) [< >] (< >) ++ -- %= != || :: << >> !! ... <<= >>=","title":"Operators and punctuation"},{"location":"specification/#integer-literals","text":"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","title":"Integer literals"},{"location":"specification/#floating-point-literals","text":"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","title":"Floating point literals"},{"location":"specification/#characters","text":"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","title":"Characters"},{"location":"specification/#backslash-escapes","text":"The following backslash escapes are available for characters and string literals: \\0 0x00 zero value \\a 0x07 alert/bell \\b 0x08 backspace \\e 0x1B escape \\f 0x0C form feed \\n 0x0A newline \\r 0x0D carriage return \\t 0x09 horizontal tab \\v 0x0B vertical tab \\\\ 0x5C backslash \\' 0x27 single quote ' \\\" 0x22 double quote \" \\x Escapes a single byte hex value \\u Escapes a two byte unicode hex value \\U Escapes a four byte unicode hex value","title":"Backslash escapes"},{"location":"specification/#string-literals","text":"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 , \\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","title":"String literals"},{"location":"specification/#compile-time-string-concatenation","text":"Strings will concatenate if declared in sequence. Example: String s = \"abc\" \"def\" \"ghi\"; // This is equivalent to: String s = \"abcdefghi\";","title":"Compile time string concatenation"},{"location":"specification/#raw-string-literals","text":"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.","title":"Raw string literals"},{"location":"specification/#compile-time-concatenation","text":"Raw strings will concatenate with other regular strings and raw strings ( see string literal compile time concatenation ).","title":"Compile time concatenation"},{"location":"specification/#source-code-pre-filtering","text":"The source code will pre-filter \\r ( 0x0D ) from the source code. This means that it is also implicitly filtered out of raw strings.","title":"Source code pre-filtering"},{"location":"specification/#character-literals","text":"A character literal is enclosed in ' and may either consist of 1, 2, 4, 8, 16 bytes. CHARACTER_LIT ::= \"'\" (CHAR_LIT_BYTE+) | UNICODE_CHAR \"'\"","title":"Character literals"},{"location":"specification/#types","text":"Types consist of built-in types and user-defined types (enums, structs, unions, bitstructs, fault and distinct).","title":"Types"},{"location":"specification/#boolean-types","text":"bool may have the two values true and false . It holds a single bit of information but is stored in a char type.","title":"Boolean types"},{"location":"specification/#integer-types","text":"The built-in integer types: char unsigned 8-bit ichar signed 8-bit ushort unsigned 16-bit short signed 16-bit uint unsigned 32-bit int signed 32-bit ulong unsigned 64-bit long signed 64-bit uint128 unsigned 128-bit int128 singed 128-bit In addition, the following type aliases exist: uptr unsigned pointer size iptr signed pointer size usz unsigned pointer offset / object size isz signed pointer offset / object size","title":"Integer types"},{"location":"specification/#floating-point-types","text":"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)","title":"Floating point types"},{"location":"specification/#vector-types","text":"A vector lowers to the platform's vector types where available. A vector has a base type and a width. vector_type ::= type \"[<\" length \">]\"","title":"Vector types"},{"location":"specification/#vector-base-type","text":"The base type of a vector must be boolean, an integer or a floating point type.","title":"Vector base type"},{"location":"specification/#min-width","text":"The vector width must be at least 1.","title":"Min width"},{"location":"specification/#element-access","text":"Vector elements are accessed using [] . It is possible to take the address of a single element.","title":"Element access"},{"location":"specification/#alignment","text":"Alignment of vectors are platform dependent, but is at least the alignment of its element type.","title":"Alignment"},{"location":"specification/#vector-operations","text":"Vectors support the same arithmetics as its underlying type, and will perform the operation element-wise. Example: int[<2>] a = { 1, 3 }; int[<2>] b = { 2, 7 }; int[<2>] c = a * b; // Equivalent to int[<2>] c = { a[0] * b[0], a[1] * b[1] };","title":"Vector operations"},{"location":"specification/#array-types","text":"An array has the alignment of its elements. An array must have at least one element.","title":"Array types"},{"location":"specification/#subarray-types","text":"The subarray consist of a pointer, followed by an usz length, having the alignment of pointers.","title":"Subarray types"},{"location":"specification/#pointer-types","text":"A pointer is the address to memory. pointer_type ::= type \"*\"","title":"Pointer types"},{"location":"specification/#pointee-type","text":"The type of the memory pointed to is the pointee type . It may be any runtime type.","title":"Pointee type"},{"location":"specification/#iptr-and-uptr","text":"A pointer may be losslessly cast to an iptr or uptr . An iptr or uptr may be cast to a pointer of any type.","title":"iptr and uptr"},{"location":"specification/#the-wildcard-pointer-void","text":"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.","title":"The wildcard pointer void*"},{"location":"specification/#pointer-arithmetic-on-void","text":"Performing pointer arithmetics on void* will assume that the element size is 1. This includes pointer arithmetics using subscripting.","title":"Pointer arithmetic on void*"},{"location":"specification/#subscripting","text":"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.","title":"Subscripting"},{"location":"specification/#deref","text":"Dereferencing a pointer will return the value in the memory location interpreted as the pointee type .","title":"Deref"},{"location":"specification/#_1","text":"","title":""},{"location":"specification/#struct-types","text":"A struct may not have zero members.","title":"Struct types"},{"location":"specification/#alignment_1","text":"A non-packed struct has the alignment of the member that has the highest alignment. A packed struct has alignment 1. See align attribute for details on changing the alignment.","title":"Alignment"},{"location":"specification/#union-types","text":"A union may not have zero members.","title":"Union types"},{"location":"specification/#alignment_2","text":"A union has the alignment of the member that has the highest alignment. See align attribute for details on changing the alignment.","title":"Alignment"},{"location":"specification/#fault-types","text":"A fault is an extensible enum which can be used to create an optional .","title":"Fault types"},{"location":"specification/#alignment_3","text":"A fault type has the same alignment as a pointer. See align attribute for details on changing the alignment.","title":"Alignment"},{"location":"specification/#enum-types","text":"","title":"Enum types"},{"location":"specification/#function-types","text":"","title":"Function types"},{"location":"specification/#typeid-type","text":"The typeid is a pointer sized value which uniquely identifies a type.","title":"Typeid type"},{"location":"specification/#any-type","text":"The any* is a fat pointer (2 pointers wide) holding a pointer to a value and its corresponding typeid . It cannot be dereferenced.","title":"Any* type"},{"location":"specification/#fields","text":".ptr returns a void* pointer to the underlying value .type returns the typeid of the underlying value.","title":"Fields"},{"location":"specification/#switching-over-any","text":"Switching over an any value creates an any switch .","title":"Switching over any"},{"location":"specification/#anyfault-type","text":"","title":"Anyfault type"},{"location":"specification/#declarations-and-scope","text":"","title":"Declarations and scope"},{"location":"specification/#expressions","text":"","title":"Expressions"},{"location":"specification/#assignment-expression","text":"assignment_expr ::= ct_type_assign | unary_expr assignment_op expr ct_type_assign ::= ct_type_ident \"=\" type assignment_op ::= \"=\" | \"+=\" | \"-=\" | \"*=\" | \"/=\" | \"%=\" | \"<<=\" | \">>=\" | \"&=\" | \"^=\" | \"|=\"","title":"Assignment expression"},{"location":"specification/#type-assign","text":"This assigns a new type to a compile time type variable. The value of the expression is the type assigned.","title":"Type assign"},{"location":"specification/#combined-assign","text":"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.","title":"Combined assign"},{"location":"specification/#implicit-conversion","text":"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.","title":"Implicit conversion"},{"location":"specification/#ternary-elvis-and-or-else-expressions","text":"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","title":"Ternary, elvis and or-else expressions"},{"location":"specification/#ternary-evaluation","text":"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 .","title":"Ternary evaluation"},{"location":"specification/#elvis-evaluation","text":"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 .","title":"Elvis evaluation"},{"location":"specification/#orelse-evaluation","text":"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.","title":"Orelse evaluation"},{"location":"specification/#suffix-expression","text":"Suffix expressions convert a fault to an optional. suffix_group_exp ::= or_group_expr | suffix_expr suffix_expr ::= or_group_expr \"?\" \"!\"?","title":"Suffix expression"},{"location":"specification/#effect-of","text":"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.","title":"Effect of \"?\""},{"location":"specification/#type-of-the-expression","text":"The type is a wildcard optional . If \"!\" is added, it is a wildcard type.","title":"Type of the expression"},{"location":"specification/#rethrow-expression","text":"If the expression is optional, implicitly return with the optional value. rethrow_expr ::= expr \"!\"","title":"Rethrow expression"},{"location":"specification/#the-expression-to-rethrow","text":"The expression must have an optional type, otherwise this is a compile time error.","title":"The expression to rethrow"},{"location":"specification/#type","text":"The type of \"rethrow\" is the inner expr type without optional.","title":"Type"},{"location":"specification/#relational-expression","text":"rel_group_expr ::= add_group_expr | relational_expr relational_expr ::= rel_group_expr relational_op add_group_expr relational_op ::= \"<\" | \">\" | \"<=\" | \">=\" TODO","title":"Relational expression"},{"location":"specification/#and-expression","text":"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","title":"And expression"},{"location":"specification/#type_1","text":"The type of the and-expression is bool .","title":"Type"},{"location":"specification/#or-expression","text":"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","title":"Or expression"},{"location":"specification/#type_2","text":"The type of the or-expression is bool .","title":"Type"},{"location":"specification/#casts","text":"","title":"Casts"},{"location":"specification/#pointer-casts","text":"","title":"Pointer casts"},{"location":"specification/#integer-to-pointer-cast","text":"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.","title":"Integer to pointer cast"},{"location":"specification/#pointer-to-integer-cast","text":"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","title":"Pointer to integer cast"},{"location":"specification/#subscript-operator","text":"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.","title":"Subscript operator"},{"location":"specification/#operands","text":"","title":"Operands"},{"location":"specification/#compound-literals","text":"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.","title":"Compound Literals"},{"location":"specification/#function-calls","text":"","title":"Function calls"},{"location":"specification/#varargs","text":"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.","title":"Varargs"},{"location":"specification/#statements","text":"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","title":"Statements"},{"location":"specification/#asm-block-statement","text":"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","title":"Asm block statement"},{"location":"specification/#assert-statement","text":"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)*","title":"Assert statement"},{"location":"specification/#conditional-inclusion","text":"assert statements are only included in \"safe\" builds. They may turn into assume directives for the compiler on \"fast\" builds.","title":"Conditional inclusion"},{"location":"specification/#assert-message","text":"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.","title":"Assert message"},{"location":"specification/#panic-function","text":"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.","title":"Panic function"},{"location":"specification/#break-statement","text":"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? \";\"","title":"Break statement"},{"location":"specification/#break-labels","text":"If a break has a label, then it will instead exit an outer scope with the label.","title":"Break labels"},{"location":"specification/#unreachable-code","text":"Any statement following break in the same scope is considered unreachable.","title":"Unreachable code"},{"location":"specification/#compile-time-echo-statement","text":"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 \";\"","title":"Compile time echo statement"},{"location":"specification/#the-message","text":"The message must be a compile time constant string.","title":"The message"},{"location":"specification/#compile-time-assert-statement","text":"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) \";\"","title":"Compile time assert statement"},{"location":"specification/#evaluated-expression","text":"The checked expression must evaluate to a boolean compile time constant.","title":"Evaluated expression"},{"location":"specification/#error-message","text":"The second parameter, which is optional, must evaluate to a constant string.","title":"Error message"},{"location":"specification/#compile-time-error-statement","text":"During parsing, when semantically checked this statement will output a compile time error with the message given. ct_error_stmt ::= \"$error\" constant_expr \";\"","title":"Compile time error statement"},{"location":"specification/#error-message_1","text":"The parameter must evaluate to a constant string.","title":"Error message"},{"location":"specification/#compile-time-if-statement","text":"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\"","title":"Compile time if statement"},{"location":"specification/#cond-expression","text":"The cond expression must be possible to evaluate to true or false at compile time.","title":"Cond expression"},{"location":"specification/#scopes","text":"The \"then\" and \"else\" branches will add a compile time scope that is exited when reaching $endif . It adds no runtime scope.","title":"Scopes"},{"location":"specification/#evaluation","text":"Statements in the branch not picked will not be semantically checked.","title":"Evaluation"},{"location":"specification/#compile-time-switch-statement","text":"ct_switch_stmt ::= \"$switch\" (\"(\" ct_expr_or_type \")\")? ct_case_stmt+ \"$endswitch\" ct_case_stmt ::= (\"$default\" | \"$case\" ct_expr_or_type) \":\" stmt*","title":"Compile time switch statement"},{"location":"specification/#no-cond-expression-switch","text":"If the cond expression is missing, evaluation will go through each case until one case expression evaluates to true.","title":"No cond expression switch"},{"location":"specification/#type-expressions","text":"If a cond expression is a type, then all case statement expressions must be types as well.","title":"Type expressions"},{"location":"specification/#ranged-cases","text":"Compile time switch does not support ranged cases.","title":"Ranged cases"},{"location":"specification/#fallthrough","text":"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.","title":"Fallthrough"},{"location":"specification/#break-and-nextcase","text":"Compile time switches do not support break nor nextcase .","title":"Break and nextcase"},{"location":"specification/#evaluation-of-statements","text":"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.","title":"Evaluation of statements"},{"location":"specification/#continue-statement","text":"A continue statement jumps to the cond expression of a while , for , do or foreach continue_stmt ::= \"continue\" label? \";\"","title":"Continue statement"},{"location":"specification/#continue-labels","text":"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.","title":"Continue labels"},{"location":"specification/#unreachable-code_1","text":"Any statement following continue in the same scope is considered unreachable.","title":"Unreachable code"},{"location":"specification/#declaration-statement","text":"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)?","title":"Declaration statement"},{"location":"specification/#thread-local-storage","text":"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.","title":"Thread local storage"},{"location":"specification/#static-storage","text":"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.","title":"Static storage"},{"location":"specification/#scopes_1","text":"Runtime variables are added to the runtime scope, compile time variables to the compile time scope. See var statements .","title":"Scopes"},{"location":"specification/#multiple-declarations","text":"If more than one variable is declared, no init expressions are allowed for any of the variables.","title":"Multiple declarations"},{"location":"specification/#no-init-expression","text":"If no init expression is provided, the variable is zero initialized .","title":"No init expression"},{"location":"specification/#opt-out-of-zero-initialization","text":"Using the @noinit attribute opts out of zero initialization .","title":"Opt-out of zero initialization"},{"location":"specification/#self-referencing-initialization","text":"An init expression may refer to the address of the same variable that is declared, but not the value of the variable. Example: void* a = &a; // Valid int a = a + 1; // Invalid","title":"Self referencing initialization"},{"location":"specification/#defer-statement","text":"The defer statements are executed at (runtime) scope exit, whether through return , break , continue or rethrow. defer_stmt ::= \"defer\" (\"try\" | \"catch\")? stmt","title":"Defer statement"},{"location":"specification/#defer-in-defer","text":"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.","title":"Defer in defer"},{"location":"specification/#static-and-tlocal-variables-in-defer","text":"Static and tlocal variables are allowed in a defer statement. Only a single variable is instantiated regardless of the number of inlining locations.","title":"Static and tlocal variables in defer"},{"location":"specification/#defer-and-return","text":"If the return has an expression, then it is evaluated before the defer statements (due to exit from the current function scope), are executed. Example: int a = 0; defer a++; return a; // This is equivalent to int a = 0; int temp = a; a++; return temp;","title":"Defer and return"},{"location":"specification/#defer-and-jump-statements","text":"A defer body may not contain a break , continue , return or rethrow that would exit the statement.","title":"Defer and jump statements"},{"location":"specification/#defer-execution","text":"Defer statements are executed in the reverse order of their declaration, starting from the last declared defer statement.","title":"Defer execution"},{"location":"specification/#defer-try","text":"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.","title":"Defer try"},{"location":"specification/#defer-catch","text":"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.","title":"Defer catch"},{"location":"specification/#non-regular-returns-longjmp-panic-and-other-errors","text":"Defers will not execute when doing longjmp terminating through a panic or other error. They are only invoked on regular scope exits.","title":"Non-regular returns - longjmp, panic and other errors"},{"location":"specification/#expr-statement","text":"An expression statement evaluates an expression. expr_stmt ::= expr \";\"","title":"Expr statement"},{"location":"specification/#no-discard","text":"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.","title":"No discard"},{"location":"specification/#if-statement","text":"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)","title":"If statement"},{"location":"specification/#scopes_2","text":"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.","title":"Scopes"},{"location":"specification/#special-parsing-of-the-then-clause","text":"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.","title":"Special parsing of the \"then\" clause"},{"location":"specification/#break","text":"It is possible to use labelled break to break out of an if statement. Note that an unlabelled break may not be used.","title":"Break"},{"location":"specification/#if-try","text":"The cond expression may be a try-unwrap chain. In this case, the unwrapped variables are scoped to the \"then\" clause only.","title":"If-try"},{"location":"specification/#if-catch","text":"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. }","title":"If-catch"},{"location":"specification/#if-catch-implicit-unwrap","text":"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.","title":"If-catch implicit unwrap"},{"location":"specification/#nextcase-statement","text":"Nextcase will jump to another switch case. nextcase_stmt ::= \"nextcase\" ((label \":\")? (expr | \"default\"))? \";\"","title":"Nextcase statement"},{"location":"specification/#labels","text":"When a nextcase has a label, the jump is to the switch in an outer scope with the corresponding label.","title":"Labels"},{"location":"specification/#no-expression-jumps","text":"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.","title":"No expression jumps"},{"location":"specification/#jumps-to-default","text":"Using default jumps to the default clause of a switch.","title":"Jumps to default"},{"location":"specification/#missing-case","text":"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.","title":"Missing case"},{"location":"specification/#variable-expression","text":"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.","title":"Variable expression"},{"location":"specification/#unreachable-code_2","text":"Any statement in the same scope after a nextcase are considered unreachable .","title":"Unreachable code"},{"location":"specification/#switch-statement","text":"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*","title":"Switch statement"},{"location":"specification/#regular-switch","text":"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.","title":"Regular switch"},{"location":"specification/#if-switch","text":"If the cond expression is missing or the case statements are non-constant expressions, then each case clause will be evaluated in order after the cond expression has been evaluated (if it exists): If a cond expression exists, calculate the case expression and execute the case if it is matching the cond expression. A default statement has no expression and will always be considered matching the cond expression reached. If no con expression exists, calculate the case expression and execute the case if the expression evaluates to \"true\" when implicitly converted to boolean. A default statement will always be considered having the \"true\" result.","title":"If-switch"},{"location":"specification/#any-switch","text":"If the cond expression is an any* type, the switch is handled as if switching was done over the type field of the any* . This field has the type of typeid , and the cases follows the rules for switching over typeid . If the cond expression is a variable, then this variable is implicitly converted to a pointer with the pointee type given by the case statement. Example: any* a = abc(); switch (a) { case int: int b = *a; // a is int* case float: float z = *a; // a is float* case Bar: Bar f = *a; // a is Bar* default: // a is not unwrapped }","title":"Any-switch"},{"location":"specification/#ranged-cases_1","text":"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.","title":"Ranged cases"},{"location":"specification/#fallthrough_1","text":"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.","title":"Fallthrough"},{"location":"specification/#exhaustive-switch","text":"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.","title":"Exhaustive switch"},{"location":"specification/#break_1","text":"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.","title":"Break"},{"location":"specification/#unreachable-code_3","text":"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 .","title":"Unreachable code"},{"location":"specification/#switching-over-typeid","text":"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 .","title":"Switching over typeid"},{"location":"specification/#nextcase-without-expression","text":"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.","title":"Nextcase without expression"},{"location":"specification/#nextcase-with-expression","text":"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.","title":"Nextcase with expression"},{"location":"specification/#do-statement","text":"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 \")\")? \";\"","title":"Do statement"},{"location":"specification/#unreachable-code_4","text":"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.","title":"Unreachable code"},{"location":"specification/#break_2","text":"break will exit the do with execution continuing on the following statement.","title":"Break"},{"location":"specification/#continue","text":"continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached.","title":"Continue"},{"location":"specification/#do-block","text":"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\"","title":"Do block"},{"location":"specification/#for-statement","text":"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?","title":"For statement"},{"location":"specification/#init-expression","text":"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.","title":"Init expression"},{"location":"specification/#incr-expression","text":"The incr expression is evaluated before evaluating the cond expr every time except for the first one. The incr expression may optionally be omitted.","title":"Incr expression"},{"location":"specification/#cond-expression_1","text":"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 .","title":"Cond expression"},{"location":"specification/#unreachable-code_5","text":"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.","title":"Unreachable code"},{"location":"specification/#break_3","text":"break will exit the for with execution continuing on the following statement after the for .","title":"Break"},{"location":"specification/#continue_1","text":"continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached.","title":"Continue"},{"location":"specification/#equivalence-of-while-and-for","text":"A while loop is functionally equivalent to a for loop without init and incr expressions.","title":"Equivalence of while and for"},{"location":"specification/#foreach-and-foreach_r-statements","text":"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","title":"Foreach and foreach_r statements"},{"location":"specification/#break_4","text":"break will exit the foreach statement with execution continuing on the following statement after.","title":"Break"},{"location":"specification/#continue_2","text":"continue will cause the next iteration to commence, as if the end of the statement had been reached.","title":"Continue"},{"location":"specification/#iteration-by-value-or-reference","text":"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.","title":"Iteration by value or reference"},{"location":"specification/#foreach-variable","text":"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.","title":"Foreach variable"},{"location":"specification/#foreach-index","text":"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.","title":"Foreach index"},{"location":"specification/#foreach-support","text":"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.","title":"Foreach support"},{"location":"specification/#return-statement","text":"The return statement evaluates its expression (if present) and returns the result. return_stmt ::= \"return\" expr? \";\"","title":"Return statement"},{"location":"specification/#jumps-in-return-statements","text":"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;","title":"Jumps in return statements"},{"location":"specification/#return-from-expression-blocks","text":"A return from an expression block only returns out of the expression block, it never returns from the expression block's enclosing scope.","title":"Return from expression blocks"},{"location":"specification/#empty-returns","text":"An empty return is equivalent to a return with a void type. Consequently constructs like foo(); return; and return (void)foo(); are equivalent.","title":"Empty returns"},{"location":"specification/#unreachable-code_6","text":"Any statement directly following a return in the same scope are considered unreachable.","title":"Unreachable code"},{"location":"specification/#while-statement","text":"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","title":"While statement"},{"location":"specification/#unreachable-code_7","text":"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.","title":"Unreachable code"},{"location":"specification/#break_5","text":"break will exit the while with execution continuing on the following statement.","title":"Break"},{"location":"specification/#continue_3","text":"continue will jump directly to the evaluation of the cond, as if the end of the statement had been reached.","title":"Continue"},{"location":"specification/#var-statement","text":"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)? \";\"","title":"Var statement"},{"location":"specification/#inferring-type","text":"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.","title":"Inferring type"},{"location":"specification/#scope","text":"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 ).","title":"Scope"},{"location":"specification/#attributes","text":"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","title":"Attributes"},{"location":"specification/#user-defined-attributes","text":"User defined attributes group a list of attributes. attribute_decl ::= \"def\" AT_TYPE_IDENT (\"(\" parameters \")\")? attribute* \"=\" \"{\" attribute* \"}\" \";\"","title":"User defined attributes"},{"location":"specification/#empty-list-of-attributes","text":"The list of attributes may be empty.","title":"Empty list of attributes"},{"location":"specification/#parameter-arguments","text":"Arguments given to user defined attributes will be passed on to the attributes in the list.","title":"Parameter arguments"},{"location":"specification/#expansion","text":"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.","title":"Expansion"},{"location":"specification/#nesting","text":"A user defined attribute can contain other user defined attributes. The definition may not be cyclic.","title":"Nesting"},{"location":"specification/#modules","text":"Module paths are hierarchal, with each sub-path appended with '::' + the name: path ::= PATH_SEGMENT (\"::\" PATH_SEGMENT) Each module declaration starts its own module section . All imports and all @local declarations are only visible in the current module section . module_section ::= \"module\" path opt_generic_params? attributes? \";\" generic_param ::= TYPE_IDENT | CONST_IDENT opt_generic_params ::= \"(<\" generic_param (\",\" generic_param)* \">)\" Any visibility attribute defined in a module section will be the default visibility in all declarations in the section. If the @benchmark attribute is applied to the module section then all function declarations will implicitly have the @benchmark attribute. If the @test attribute is applied to the module section then all function declarations will implicitly have the @test attribute.","title":"Modules"},{"location":"standard_library/","text":"Library The standard library is currently in development, so frequent changes will occur. Note that all std::core modules and sub modules are implicitly imported. std::core::builtin All functions and macros in this library can be used without path qualifiers. void panic(char message, char file, char *function, uint line) Default function called when the asserts fails. void @swap(&a, &b) Swap values in a and b . int a = 3; int b = 5; @swap(a, b); io::printfn(\"%d\", a); // Prints 5 varcast(any* v, $Type) Optionally cast the value v to type $Type* on failure returns VarCastResult.TYPE_MISMATCH . 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. switch (x) { case 0: foo(); case 1: bar(); default: // Should never happen. unreachable(\"x should have been 0 or 1\"); } On safe mode this will throw a runtime panic when reached. For release mode the compiler will assume this case never happens. bitcast(value, $Type) Do a bitcast of a value to $Type , requires that the types are of the same memory size. float x = 1.0; int y = bitcast(x, int); // y = 0x3f800000 enum_by_name($Type, enum_name) Optionally returns the enum value with the given name. $Type must be an enum. Returns SearchResult.MISSING on failure. enum Foo { ABC, CDE, EFG } fn void! test() { Foo f = enum_by_name(Foo, \"CDE\")!; // same as Foo f = Foo.CDE; } void @scope(&variable; @body) Scopes a variable: int a = 3; @scope(a) { a = 4; a++; }; // Prints a = 3 io::printfn(\"a = %d\", a, b); less, greater, less_eq, greater_eq, equals All macros take two values and compare them. Any type implementing Type.less or Type.compare_to may be compared (or if the type implements < ). Types implementing Type.equals may use equals even if neither less nor compare_to are implemented. Faults IteratorResult returned when reaching the end of an iterator. SearchResult used when a search fails. 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. 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. int* int = malloc(int); int[] ints = new_array(int, 100); // Allocated int[100] on the heap. 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. 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. @poo() { // This allocation uses the dynamic arena Foo* f = talloc(Foo); }; @volatile_load(&x) Returns the value in x using a volatile load. // Both loads will always happen: int y = @volatile_load(my_global); y = @volatile_load(my_global); @volatile_store(&x, y) Store the value y in x using a volatile store. // Both stores will always happen: @volatile_store(y, 1); @volatile_store(y, 1); usz aligned_offset(usz offset, usz alignment) Returns an aligned size based on the current offset. The alignment must be a power of two. E.g. mem::aligned_offset(17, 8) would return 24 usz aligned_pointer(void* ptr, usz alignment) Returns a pointer aligned to the given alignment, using aligned_offset . bool ptr_is_aligned(void* ptr, usz alignment) Return true if the pointer is aligned, false otherwise. void copy(void dst, void src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false) Copies bytes from one pointer to another. It may optionally be set as volatile, in which case the copy may not be optimized away. Furthermore the source and destination alignment may be used. Foo* f = 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. 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. 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 . 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.","title":"Standard Library"},{"location":"standard_library/#library","text":"The standard library is currently in development, so frequent changes will occur. Note that all std::core modules and sub modules are implicitly imported.","title":"Library"},{"location":"standard_library/#stdcorebuiltin","text":"All functions and macros in this library can be used without path qualifiers.","title":"std::core::builtin"},{"location":"standard_library/#void-panicchar-message-char-file-char-function-uint-line","text":"Default function called when the asserts fails.","title":"void panic(char message, char file, char *function, uint line)"},{"location":"standard_library/#void-swapa-b","text":"Swap values in a and b . int a = 3; int b = 5; @swap(a, b); io::printfn(\"%d\", a); // Prints 5","title":"void @swap(&a, &b)"},{"location":"standard_library/#varcastany-v-type","text":"Optionally cast the value v to type $Type* on failure returns VarCastResult.TYPE_MISMATCH . int b; any* a = &b; float*! c = varcast(a, float); // Will return TYPE_MISMATCH int*! d = varcast(a, int); // Works!","title":"varcast(any* v, $Type)"},{"location":"standard_library/#void-unreachablestring-unreachable-statement-reached","text":"Mark a code path as unreachable. switch (x) { case 0: foo(); case 1: bar(); default: // Should never happen. unreachable(\"x should have been 0 or 1\"); } On safe mode this will throw a runtime panic when reached. For release mode the compiler will assume this case never happens.","title":"void unreachable($string = \"Unreachable statement reached.\")"},{"location":"standard_library/#bitcastvalue-type","text":"Do a bitcast of a value to $Type , requires that the types are of the same memory size. float x = 1.0; int y = bitcast(x, int); // y = 0x3f800000","title":"bitcast(value, $Type)"},{"location":"standard_library/#enum_by_nametype-enum_name","text":"Optionally returns the enum value with the given name. $Type must be an enum. Returns SearchResult.MISSING on failure. enum Foo { ABC, CDE, EFG } fn void! test() { Foo f = enum_by_name(Foo, \"CDE\")!; // same as Foo f = Foo.CDE; }","title":"enum_by_name($Type, enum_name)"},{"location":"standard_library/#void-scope-body","text":"Scopes a variable: int a = 3; @scope(a) { a = 4; a++; }; // Prints a = 3 io::printfn(\"a = %d\", a, b);","title":"void @scope(&variable; @body)"},{"location":"standard_library/#less-greater-less_eq-greater_eq-equals","text":"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.","title":"less, greater, less_eq, greater_eq, equals"},{"location":"standard_library/#faults","text":"IteratorResult returned when reaching the end of an iterator. SearchResult used when a search fails. AnyCastResult when a any cast fails.","title":"Faults"},{"location":"standard_library/#stdcoreenv","text":"","title":"std::core::env"},{"location":"standard_library/#constants","text":"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.","title":"Constants"},{"location":"standard_library/#stdcoremem","text":"","title":"std::core::mem"},{"location":"standard_library/#malloc-malloc_checked-malloc_aligned","text":"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. 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);","title":"malloc, malloc_checked, malloc_aligned"},{"location":"standard_library/#newtype-new_arraytype-usz-elements","text":"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. int* int = malloc(int); int[] ints = new_array(int, 100); // Allocated int[100] on the heap. 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);","title":"new($Type), new_array($Type, usz elements)"},{"location":"standard_library/#calloc-calloc_checked-calloc_aligned","text":"Identical to the malloc variants, except the data is guaranteed to be zeroed out.","title":"calloc, calloc_checked, calloc_aligned"},{"location":"standard_library/#relloc-relloc_checked-realloc_aligned","text":"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 .","title":"relloc, relloc_checked, realloc_aligned"},{"location":"standard_library/#free-free_aligned","text":"Frees memory allocated using malloc or calloc . Any memory allocated using \"_aligned\" variants must be freed using free_aligned .","title":"free, free_aligned"},{"location":"standard_library/#scopedallocator-allocator-body","text":"Swaps the current memory allocator for the duration of the call. DynamicArenaAllocator dynamic_arena; dynamic_arena.init(1024); mem::@scoped(&dynamic_arena) { // This allocation uses the dynamic arena Foo* f = malloc(Foo); }; // Release any dynamic arena memory. dynamic_arena.destroy();","title":"@scoped(Allocator* allocator; @body())"},{"location":"standard_library/#tscoped-body","text":"Same as @scoped, but uses the temporary allocator rather than any arbitrary allocator.","title":"@tscoped(; @body())"},{"location":"standard_library/#void-tmallocusz-size-usz-alignment-0","text":"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.","title":"void* tmalloc(usz size, usz alignment = 0)"},{"location":"standard_library/#void-tcallocusz-size-usz-alignment-0","text":"Same as tmalloc but clears the memory.","title":"void* tcalloc(usz size, usz alignment = 0)"},{"location":"standard_library/#void-treallocvoid-ptr-usz-size-usz-alignment-0","text":"realloc but on memory received using tcalloc or tmalloc .","title":"void trealloc(void ptr, usz size, usz alignment = 0)"},{"location":"standard_library/#void-poolbody","text":"Opens a temporary memory scope. @poo() { // This allocation uses the dynamic arena Foo* f = talloc(Foo); };","title":"void @pool(;@body)"},{"location":"standard_library/#volatile_loadx","text":"Returns the value in x using a volatile load. // Both loads will always happen: int y = @volatile_load(my_global); y = @volatile_load(my_global);","title":"@volatile_load(&x)"},{"location":"standard_library/#volatile_storex-y","text":"Store the value y in x using a volatile store. // Both stores will always happen: @volatile_store(y, 1); @volatile_store(y, 1);","title":"@volatile_store(&x, y)"},{"location":"standard_library/#usz-aligned_offsetusz-offset-usz-alignment","text":"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","title":"usz aligned_offset(usz offset, usz alignment)"},{"location":"standard_library/#usz-aligned_pointervoid-ptr-usz-alignment","text":"Returns a pointer aligned to the given alignment, using aligned_offset .","title":"usz aligned_pointer(void* ptr, usz alignment)"},{"location":"standard_library/#bool-ptr_is_alignedvoid-ptr-usz-alignment","text":"Return true if the pointer is aligned, false otherwise.","title":"bool ptr_is_aligned(void* ptr, usz alignment)"},{"location":"standard_library/#void-copyvoid-dst-void-src-usz-len-usz-dst_align-0-usz-src_align-0-bool-is_volatile-false","text":"Copies bytes from one pointer to another. It may optionally be set as volatile, in which case the copy may not be optimized away. Furthermore the source and destination alignment may be used. Foo* f = talloc(data_size); mem::copy(f, slice.ptr, size);","title":"void copy(void dst, void src, usz len, usz $dst_align = 0, usz $src_align = 0, bool $is_volatile = false)"},{"location":"standard_library/#void-setvoid-dst-char-val-usz-len-usz-dst_align-0-bool-is_volatile-false","text":"Sets bytes to a value. This operation may be aligned and/or volatile. See the copy method.","title":"void set(void* dst, char val, usz len, usz $dst_align = 0, bool $is_volatile = false)"},{"location":"standard_library/#void-clearvoid-dst-usz-len-usz-dst_align-0-bool-is_volatile-false","text":"Sets bytes to zero. This operation may be aligned and/or volatile. See the copy method.","title":"void clear(void* dst, usz len, usz $dst_align = 0, bool $is_volatile = false)"},{"location":"standard_library/#clonevalue","text":"Makes a shallow copy of a value using the regular allocator. Foo f = ... return @clone(f);","title":"@clone(&value)"},{"location":"standard_library/#tclonevalue","text":"Same as @clone but uses the temporary allocator.","title":"@tclone(&value)"},{"location":"standard_library/#stdcoretypes","text":"","title":"std::core::types"},{"location":"standard_library/#bool-is_comparabletype","text":"Return true if the type can be used with comparison operators.","title":"bool is_comparable($Type)"},{"location":"standard_library/#bool-is_equatable_valuevalue","text":"Return true if the value can be compared using the equals macro.","title":"bool is_equatable_value(value)"},{"location":"standard_library/#bool-is_equatable_valuevalue_1","text":"Return true if the value can be compared using the comparison macros.","title":"bool is_equatable_value(value)"},{"location":"standard_library/#kind_is_inttypekind-kind","text":"","title":"kind_is_int(TypeKind kind)"},{"location":"standard_library/#any_to_intany-v-type","text":"Returns an optional value of $Type if the any value losslessly may be converted into the given type. Returns a ConversionResult otherwise. any* v = &&128; short y = any_to_int(v, short)!!; // Works ichar z = any_to_int(v, ichar)!!; // Panics VALUE_OUT_OF_RANGE","title":"any_to_int(any* v, $Type)"},{"location":"standard_library/#stdcorestrconv","text":"","title":"std::core::str::conv"},{"location":"standard_library/#usz-char32_to_utf8char32-c-char-output-usz-available","text":"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.","title":"usz! char32_to_utf8(Char32 c, char* output, usz available)"},{"location":"standard_library/#void-char32_to_utf16_unsafechar32-c-char16-output","text":"Convert a UTF32 codepoint to an UTF16 buffer without bounds checking, moving the output pointer 1 or 2 steps.","title":"void char32_to_utf16_unsafe(Char32 c, Char16** output)"},{"location":"standard_library/#stdio","text":"","title":"std::io"},{"location":"standard_library/#usz-printfstring-format-args-maydiscard","text":"Regular printf functionality: %s , %x , %d , %f and %p are supported. Will also print enums and vectors.","title":"usz! printf(String format, args...) @maydiscard"},{"location":"standard_library/#usz-dstringappendfdstring-str-string-format-args-maydiscard","text":"Same as printf but on dynamic strings.","title":"usz! DString.appendf(DString* str, String format, args...) @maydiscard"},{"location":"standard_library/#usz-fileprintffile-file-string-format-args-maydiscard","text":"Same as printf but on files.","title":"usz! File.printf(File file, String format, args...) @maydiscard"},{"location":"standard_library/#void-fileopenfile-file-string-filename-string-mode","text":"Open a file with the given file name with the given mode (r, w etc)","title":"void! File.open(File* file, String filename, String mode)"},{"location":"standard_library/#void-fileseekfile-file-long-offset-seek-seekmode-seekset","text":"Seek in a file. Based on the libc function.","title":"void! File.seek(File *file, long offset, Seek seekMode = Seek.SET)"},{"location":"standard_library/#void-fileclosefile-file-inline","text":"Close a file, based on the libc function.","title":"void! File.close(File *file) @inline"},{"location":"standard_library/#bool-fileeoffile-file-inline","text":"True if EOF has been reached. Based on the libc function.","title":"bool File.eof(File* file) @inline"},{"location":"standard_library/#void-fileputcfile-file-char-c","text":"Write a single byte to a file. See the libc function.","title":"void! File.putc(File *file, char c)"},{"location":"standard_library/#usz-filereadfile-file-void-buffer-usz-items-usz-element_size-1","text":"Read into a buffer, based on the libc function.","title":"usz File.read(File file, void buffer, usz items, usz element_size = 1)"},{"location":"standard_library/#usz-filewritefile-file-void-buffer-usz-items-usz-element_size-1","text":"Write to a buffer, based on the libc function.","title":"usz File.write(File file, void buffer, usz items, usz element_size = 1)"},{"location":"standard_library/#stdout-stdin-stderr","text":"Return stdout, stdin and stderr respectively.","title":"stdout(), stdin(), stderr()"},{"location":"standard_library/#stdcollectionslist","text":"Generic list module, elements are of Type . import std::collections::list; def MyIntList = List(); ... MyIntList list; list.push(123); list.free();","title":"std::collections::list()"},{"location":"standard_library/#listpushlist-list-type-element-append","text":"Append a single value to the list.","title":"List.push(List *list, Type element), append(...)"},{"location":"standard_library/#type-listpoplist-list","text":"Removes and returns the last entry in the list.","title":"Type List.pop(List* list)"},{"location":"standard_library/#type-listpop_firstlist-list","text":"Removes the first entry in the list.","title":"Type List.pop_first(List *list)"},{"location":"standard_library/#void-listremove_atlist-list-usz-index","text":"Removes the entry at index .","title":"void List.remove_at(List *list, usz index)"},{"location":"standard_library/#void-listinsert_atlist-list-usz-index-type-type","text":"Inserts a value at index .","title":"void List.insert_at(List *list, usz index, Type type)"},{"location":"standard_library/#void-listpush_frontlist-list-type-type","text":"Inserts a value to the front of the list.","title":"void List.push_front(List *list, Type type)"},{"location":"standard_library/#void-listremove_lastlist-list","text":"Remove the last value of the list.","title":"void List.remove_last(List* list)"},{"location":"standard_library/#void-listremove_firstlist-list","text":"Remove the first element in the list.","title":"void List.remove_first(List *list)"},{"location":"standard_library/#type-listfirstlist-list","text":"Return the first element in the list if available.","title":"Type List.first(List list)"},{"location":"standard_library/#type-listlastlist-list","text":"Return the last element in the list if available.","title":"Type List.last(List list)"},{"location":"standard_library/#listis_emptylist-list","text":"Return true if the list is empty.","title":"List.is_empty(List *list)"},{"location":"standard_library/#usz-listlenlist-list","text":"Return the number of elements in the list.","title":"usz List.len(List *list)"},{"location":"standard_library/#type-listgetlist-list-usz-index","text":"Return the value at index .","title":"Type List.get(List *list, usz index)"},{"location":"standard_library/#void-listfreelist-list","text":"Free all memory associated with this list.","title":"void List.free(List *list)"},{"location":"standard_library/#void-listswaplist-list-usz-i-usz-j","text":"Swap two elements in the list.","title":"void List.swap(List *list, usz i, usz j)"},{"location":"statements/","text":"Statements 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(); }","title":"Statements"},{"location":"statements/#statements","text":"Statements largely work like in C, but with some additions.","title":"Statements"},{"location":"statements/#expression-blocks","text":"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); }","title":"Expression blocks"},{"location":"statements/#labelled-break-and-continue","text":"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; } } }","title":"Labelled break and continue"},{"location":"statements/#do-without-while","text":"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\"); };","title":"Do-without-while"},{"location":"statements/#nextcase-and-labelled-nextcase","text":"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.","title":"Nextcase and labelled nextcase"},{"location":"statements/#switch-cases-with-runtime-evaluation","text":"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(); }","title":"Switch cases with runtime evaluation"},{"location":"syntax/","text":"Syntax 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 %{ #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; }","title":"Grammar"},{"location":"syntax/#syntax","text":"","title":"Syntax"},{"location":"syntax/#keywords","text":"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","title":"Keywords"},{"location":"syntax/#yacc-grammar","text":"%{ #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; }","title":"Yacc grammar"},{"location":"test/","text":"Four ways to ways when you need a variably sized list in C3 In this blog post we'll review four standard ways to handle the case when you need a list with a size which is only known at runtime. Use a generic List allocated on the heap import std::io, std::collections::list; // We create a generic List that holds doubles: def DoubleList = List(); fn double test_list_on_heap(int len) { DoubleList list; // By default will allocate on the heap defer list.free(); // Free memory at exit with a defer statement. for (int i = 0; i < len; i++) { // Append each element list.push(i + 1.0); } double sum = 0; foreach (d : list) sum += d; return sum; } We can use list.init_new(len) if we have some default length in mind, otherwise it's not necessary. Use a generic List allocated with the temp allocator Here we instead use the temp allocator to allocate and manage memory. The @pool() { ... } construct will release all temporary allocations inside of the body block. fn double test_list_on_temp_allocator(int len) { @pool() { DoubleList list; list.init_temp(); // Init using the temp allocator for (int i = 0; i < len; i++) { list.push(i + 1.0); } double sum = 0; foreach (d : list) sum += d; // No need to free explicitly! return sum; }; } Allocate an array on the heap This is the conventional way to do it in C if the length is unknown. Note how we can use defer to write the allocation and the free together to avoid forgetting freeing if there are multiple exits. fn double test_array_on_heap(int len) { double[] arr = malloc(double, len); defer free(arr); // Free at function exit. for (int i = 0; i < len; i++) { arr[i] = i + 1.0; } double sum = 0; foreach (d : arr) sum += d; return sum; } Allocate an array on the temp allocator Using the temp allocator is as close to doing allocations for free if we need arbitrarily long lists: fn double test_array_on_temp_allocator(int len) { @pool() { // The array will be released when exiting `pool()` double[] arr = talloc(double, len); for (int i = 0; i < len; i++) { arr[i] = i + 1.0; } double sum = 0; foreach (d : arr) sum += d; return sum; }; } Summary We looked at four standard ways to use arbitrarily long lists in C3. Two of them used a growable list, which is important if you might not know the exact length in advance. The other two use simple arrays. This also contrasted using the temp allocator with the heap allocator. In a later blog post I'll discuss the allocators in more detail. A gist with the full code can be found here .","title":"Test"},{"location":"test/#use-a-generic-list-allocated-on-the-heap","text":"import std::io, std::collections::list; // We create a generic List that holds doubles: def DoubleList = List(); fn double test_list_on_heap(int len) { DoubleList list; // By default will allocate on the heap defer list.free(); // Free memory at exit with a defer statement. for (int i = 0; i < len; i++) { // Append each element list.push(i + 1.0); } double sum = 0; foreach (d : list) sum += d; return sum; } We can use list.init_new(len) if we have some default length in mind, otherwise it's not necessary.","title":"Use a generic List allocated on the heap"},{"location":"test/#use-a-generic-list-allocated-with-the-temp-allocator","text":"Here we instead use the temp allocator to allocate and manage memory. The @pool() { ... } construct will release all temporary allocations inside of the body block. fn double test_list_on_temp_allocator(int len) { @pool() { DoubleList list; list.init_temp(); // Init using the temp allocator for (int i = 0; i < len; i++) { list.push(i + 1.0); } double sum = 0; foreach (d : list) sum += d; // No need to free explicitly! return sum; }; }","title":"Use a generic List allocated with the temp allocator"},{"location":"test/#allocate-an-array-on-the-heap","text":"This is the conventional way to do it in C if the length is unknown. Note how we can use defer to write the allocation and the free together to avoid forgetting freeing if there are multiple exits. fn double test_array_on_heap(int len) { double[] arr = malloc(double, len); defer free(arr); // Free at function exit. for (int i = 0; i < len; i++) { arr[i] = i + 1.0; } double sum = 0; foreach (d : arr) sum += d; return sum; }","title":"Allocate an array on the heap"},{"location":"test/#allocate-an-array-on-the-temp-allocator","text":"Using the temp allocator is as close to doing allocations for free if we need arbitrarily long lists: fn double test_array_on_temp_allocator(int len) { @pool() { // The array will be released when exiting `pool()` double[] arr = talloc(double, len); for (int i = 0; i < len; i++) { arr[i] = i + 1.0; } double sum = 0; foreach (d : arr) sum += d; return sum; }; }","title":"Allocate an array on the temp allocator"},{"location":"test/#summary","text":"We looked at four standard ways to use arbitrarily long lists in C3. Two of them used a growable list, which is important if you might not know the exact length in advance. The other two use simple arrays. This also contrasted using the temp allocator with the heap allocator. In a later blog post I'll discuss the allocators in more detail. A gist with the full code can be found here .","title":"Summary"},{"location":"types/","text":"Types 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 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 \u2013 where xx is the bit width for typed integers, e.g. 1234u16 Integers may be written in decimal, but also in binary with the prefix 0b e.g. 0b0101000111011 , 0b011 in octal with the prefix 0o e.g. 0o0770 , 0o12345670 in hexadecimal with the prefix 0x e.g. 0xdeadbeef 0x7f7f7f In the case of binary, octal and hexadecimal, the type is assumed to be unsigned . Furthermore, underscore _ may be used to add space between digits to improve readability e.g. 0xFFFF_1234_4511_0000 , 123_000_101_100 TwoCC, FourCC and EightCC FourCC codes are often used to identify binary format types. C3 adds direct support for 4 character codes, but also 2 and 8 characters: 2 character strings, e.g. 'C3' , would convert to an ushort or short. 4 character strings, e.g. 'TEST' , converts to an uint or int. 8 character strings, e.g. 'FOOBAR11' converts to an ulong or long. Conversion is always done so that the character string has the correct ordering in memory. This means that the same characters may have different integer values on different architectures due to 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 . 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 . 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 . 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. \u2013 It's not allowed, only one level of dereference is done.) To change alignment and packing, optional 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 }","title":"Types"},{"location":"types/#types","text":"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.","title":"Types"},{"location":"types/#naming","text":"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.","title":"Naming"},{"location":"types/#differences-from-c","text":"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 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!```","title":"Differences from C"},{"location":"types/#basic-types","text":"Basic types are divided into floating point types, and integer types. Integer types being either signed or unsigned.","title":"Basic types"},{"location":"types/#integer-types","text":"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.","title":"Integer types"},{"location":"types/#integer-arithmetics","text":"All signed integer arithmetics uses 2's complement.","title":"Integer arithmetics"},{"location":"types/#integer-constants","text":"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 \u2013 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","title":"Integer constants"},{"location":"types/#twocc-fourcc-and-eightcc","text":"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.","title":"TwoCC, FourCC and EightCC"},{"location":"types/#base64-and-hex-data-literals","text":"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\";","title":"Base64 and hex data literals"},{"location":"types/#string-literals-and-raw-strings","text":"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`\\\"\";","title":"String literals, and raw strings"},{"location":"types/#floating-point-types","text":"Name bit size float16* 16 float 32 double 64 float128* 128 *support depends on platform","title":"Floating point types"},{"location":"types/#floating-point-constants","text":"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","title":"Floating point constants"},{"location":"types/#c-compatibility","text":"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.","title":"C compatibility"},{"location":"types/#other-built-in-types","text":"","title":"Other built-in types"},{"location":"types/#pointer-types","text":"Pointers mirror C: Foo* is a pointer to a Foo , while Foo** is a pointer to a pointer of Foo.","title":"Pointer types"},{"location":"types/#the-typeid-type","text":"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.","title":"The typeid type"},{"location":"types/#the-any-type","text":"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.","title":"The any* type"},{"location":"types/#array-types","text":"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 .","title":"Array types"},{"location":"types/#vector-types","text":"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 } .","title":"Vector types"},{"location":"types/#types-created-using-def","text":"","title":"Types created using def"},{"location":"types/#typedef","text":"Like in C, C3 has a \"typedef\" construct, def = def Int32 = int; def Vector2 = float[<2>]; ... Int32 a = 1; int b = a;","title":"\"typedef\""},{"location":"types/#function-pointer-types","text":"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! }","title":"Function pointer types"},{"location":"types/#distinct-types","text":"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' }","title":"Distinct types"},{"location":"types/#inline-distinct","text":"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' }","title":"Inline distinct"},{"location":"types/#generic-types","text":"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 .","title":"Generic types"},{"location":"types/#enum","text":"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 .","title":"Enum"},{"location":"types/#enum-type-inference","text":"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.","title":"Enum type inference"},{"location":"types/#enum-associated-values","text":"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();","title":"Enum associated values"},{"location":"types/#faults","text":"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.","title":"Faults"},{"location":"types/#optional-result-types","text":"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 .","title":"Optional Result Types"},{"location":"types/#struct-types","text":"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. \u2013 It's not allowed, only one level of dereference is done.) To change alignment and packing, optional attributes such as @packed may be used.","title":"Struct types"},{"location":"types/#struct-subtyping","text":"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.","title":"Struct subtyping"},{"location":"types/#union-types","text":"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 .","title":"Union types"},{"location":"types/#nested-sub-structs-unions","text":"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; } }","title":"Nested sub-structs / unions"},{"location":"types/#bitstructs","text":"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 }","title":"Bitstructs"},{"location":"undefinedbehaviour/","text":"Undefined behaviour Like C, C3 uses undefined behaviour. In contrast, C3 will trap - that is, print an error trace and abort \u2013 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.","title":"Undefined behaviour"},{"location":"undefinedbehaviour/#undefined-behaviour","text":"Like C, C3 uses undefined behaviour. In contrast, C3 will trap - that is, print an error trace and abort \u2013 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;","title":"Undefined behaviour"},{"location":"undefinedbehaviour/#list-of-undefined-behaviours","text":"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.","title":"List of undefined behaviours"},{"location":"undefinedbehaviour/#list-of-implementation-dependent-behaviours","text":"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","title":"List of implementation dependent behaviours"},{"location":"undefinedbehaviour/#list-of-undefined-behaviour-in-c-which-is-defined-in-c3","text":"","title":"List of undefined behaviour in C, which is defined in C3"},{"location":"undefinedbehaviour/#signed-integer-overflow","text":"Signed integer is always wrapped using 2s complement.","title":"Signed Integer Overflow"},{"location":"undefinedbehaviour/#modifying-the-intermediate-results-of-an-expression","text":"Behaves as if the intermediate result was stored in a variable on the stack.","title":"Modifying the intermediate results of an expression"},{"location":"variables/","text":"Variables 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.","title":"Variables"},{"location":"variables/#variables","text":"","title":"Variables"},{"location":"variables/#zero-init-by-default","text":"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.","title":"Zero init by default"},{"location":"vectors/","text":"Vectors 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.","title":"Vectors"},{"location":"vectors/#vectors","text":"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>] .","title":"Vectors"},{"location":"vectors/#arithmetics-on-vectors","text":"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.","title":"Arithmetics on vectors"},{"location":"vectors/#scalar-values","text":"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 }","title":"Scalar values"},{"location":"vectors/#additional-operations","text":"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.","title":"Additional operations"},{"location":"vectors/#swizzling","text":"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;","title":"Swizzling"},{"location":"vectors/#array-like-operations","text":"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.","title":"Array-like operations"}]} \ No newline at end of file diff --git a/sitemap.xml.gz b/sitemap.xml.gz index b14a2f8..ea00608 100644 Binary files a/sitemap.xml.gz and b/sitemap.xml.gz differ