SWAG

Swag Language Reference

Introduction

The swag-lang/swag/bin/reference/language module offers a foundational introduction to the syntax and usage of the Swag programming language, separate from the Swag standard modules (Std). This documentation is auto-generated from the module's source code.

For more advanced features, such as dynamic arrays, dynamic strings, or hash maps, please consult the Std.Core module documentation, as these topics are beyond the scope of the examples covered here. This guide focuses exclusively on the core elements of the Swag language.

Since reference/language is implemented as a test module, you can run it using the following commands:

swag test --workspace:c:/swag-lang/swag/bin/reference swag test -w:c:/swag-lang/swag/bin/reference

These commands will execute all test modules within the specified workspace, including this one. If you are running Swag from the workspace directory, the --workspace flag (or shorthand -w) can be omitted.

To compile and execute a specific module within the workspace, use the --module (or -m) flag:

swag test -w:c:/swag-lang/swag/bin/reference -m:test_language

Hello mad world

Let's start with the most simple version of the "hello world" example. This is a version that does not require external dependencies like the Swag standard modules.

#main is the program entry point, a special compiler function (that's why the name starts with #). It must be defined only once for a native executable. @print is an intrinsic, a special built-in function (that's why the name starts with @).

All intrinsics are part of the compiler runtime which comes with the compiler.

#main { @print("Hello mad world !\n") }

Next, a version that this time uses the Core.Console.print function in the Std.Core module. The Std.Core module would have to be imported in order to be used, but let's keep it simple.

#main { Core.Console.print("Hello mad world !", "\n") Core.Console.printf("%\n", "Hello mad world again !") }

A #run block is executed at compile time, and can make Swag behaves like a kind of a scripting language. So in the following example, the famous message will be printed by the compiler during compilation.

#run { const Msg = "Hello mad world !\n" // Creates a compiler constant of type 'string' Core.Console.print(Msg) // And call 'Console.print' at compile time }

A version that calls a nested function at compile time (only) to initialize the string constant to print.

// Brings the 'Core' namespace into scope, to avoid repeating it again and again using Core #main { #[Swag.ConstExpr] func nestedFunc() => "Hello mad world !\n" // Function short syntax // nestedFunc() can be called at compile time because it is marked with // the 'Swag.ConstExpr' attribute. const Msg = nestedFunc() Console.print(Msg) }

Now a stupid version that generates the code to do the print thanks to meta programming.

using Core #main { const Msg = "Hello mad world !\n" // The result of an '#ast' block is a string that will be compiled in place. // So this whole thing is equivalent to a simple 'Console.print(Msg)'. #ast { var sb = StrConv.StringBuilder{} sb.appendString("Console.print(Msg)") return sb.toString() } }

And finally let's be more and more crazy.

using Core #main { // #run will force the call of mySillyFunction() at compile time even if it's not marked // with #[Swag.ConstExpr] const Msg = #run mySillyFunction() Console.print(Msg) } // The attribute #[Swag.Compiler] tells Swag that this is a compile time function only. // So this function will not be exported to the final executable or module. #[Swag.Compiler] func mySillyFunction()->string { Console.print("Hello mad world at compile time !\n") // This creates a constant named 'MyConst' #ast { var sb = StrConv.StringBuilder{} sb.appendString("const MyConst = \"Hello ") sb.appendString("mad world ") sb.appendString("at runtime !\"") return sb.toString() } // We then use and return the constant created just above return MyConst }

This whole piece of code is equivalent to...

#main { Core.Console.print("Hello mad world at runtime !") }

Code structure

Source code organization

In Swag, all source files are required to use the .swg extension, with the exception of simple script files, which use the .swgs extension. All files must be encoded in UTF-8 to ensure proper handling of text and symbols.

Swag does not support the compilation of individual source files (except for .swgs script files). Instead, source code is structured within a workspace that comprises one or more modules. This approach promotes organized and modular development, allowing for efficient management of complex projects.

For example, Std is a workspace that includes all the standard modules provided by Swag.

A module can either be a dll (on Windows) or an executable. A single workspace may contain multiple modules, meaning that a workspace usually includes the modules you develop (such as your main executable) as well as all your dependencies, including any external modules.

Typically, the entire workspace is compiled together, ensuring that all modules and their dependencies are up to date and correctly integrated.

Global declaration order

The order of all top-level declarations does not matter. This feature allows you to reference constants, variables, or functions before they are defined, either within the same file or even across multiple files. This flexibility can be particularly useful in large codebases, where the natural flow of logic or readability may benefit from organizing code independently of its declaration order.

// In this example, we declare a constant 'A' and initialize it with 'B', // even though 'B' has not yet been declared or defined. const A = B // Next, we declare a constant 'B' and initialize it with 'C', // which is also not declared or defined at this point. const B = C // Finally, we declare and define 'C' as a constant of type 'u64' // (an unsigned 64-bit integer) with a value of 1. // This retroactively assigns values to both 'A' and 'B', // based on the earlier assignments. const C: u64 = 1

In this test, we demonstrate Swag's flexibility by calling the function functionDeclaredLater before it is defined. Swag's allowance for this behavior highlights the language's flexibility in handling the order of top-level declarations.

#run { // We call the function 'functionDeclaredLater' before it is declared. // Swag permits this because the order of function declarations and calls is flexible. functionDeclaredLater() } // The function is declared here, after it has already been called. func functionDeclaredLater() {}

It is important to note that this flexibility applies not only within the same file but also across multiple files. For instance, you can call a function in one file and define it in another. The global order of declarations in Swag is designed to be non-restrictive, allowing for a more intuitive organization of your code.

Comments

Swag supports both single-line and multi-line comments, allowing for flexible documentation and inline explanations within your code. This capability is essential for maintaining clarity and readability, especially in complex codebases.

// Example of a single-line comment // Single-line comments are typically used for brief explanations or notes. /* Example of a multi-line comment that spans several lines. Multi-line comments are useful when more detailed explanations are required. These comments can describe the overall purpose of a function, document complex logic, or provide additional context to developers. */ const A = 0 // A constant named 'A' is declared and assigned the value '0'. const B = /* false */ true // A constant named 'B' is assigned the value 'true', with an inline // comment indicating that 'false' was an alternative considered value.

Swag also supports nested comments within multi-line comments. This feature can be particularly useful when temporarily disabling a block of code or when adding additional notes inside an existing comment block.

/* */ Example of a nested multi-line comment */ The nested comment above is encapsulated within another multi-line comment. This demonstrates Swag's ability to handle complex comment structures without causing errors or ambiguity. */

Semicolons

Statement Termination in Swag

Unlike languages such as C/C++, there is no strict requirement to end statements with a semicolon (;). The default method of terminating a statement in Swag is simply reaching the end of a line (end of line). This feature allows for cleaner and more concise syntax, reducing visual clutter and making the code easier to read.

#test { // Declare two variables, x and y, both of type s32 (signed 32-bit integer), // and initialize them with the value 1. var x, y: s32 = 1 // Increment the value of both x and y by 1. x += 1 y += 1 // Use the '@assert' intrinsic to verify the correctness of the program logic. @assert(x == 2) // Confirms that x equals 2. An error is raised if this assertion fails. @assert(y == x) // Confirms that y equals x, which should also be 2 at this point. }

Optional Semicolons

While semicolons are optional, they can still be used if desired. In some cases, using semicolons might enhance the clarity of the code or improve readability, particularly when dealing with more complex statements or when writing multiple statements on a single line.

#test { // Here, type inference is used, so the types of x and y are not explicitly defined. var x, y = 0; // Both x and y are initialized to 0, with their types inferred automatically. // The following statement increments both x and y by 1 simultaneously. x, y += 1; // Both x and y now have the value 1. }

Multiple Statements on a Single Line

Semicolons become particularly useful when you need to write multiple statements on a single line. Although this can make the code more compact, it is advisable to use this approach sparingly, as excessive use may negatively impact code readability.

#test { // Two variable declarations and initializations on a single line. var x = 0; var y = 0 // Increment both x and y by 1, all on the same line. x += 1; y += 1 // Assert the correctness of both x and y values on the same line. @assert(x == 1); @assert(y == 1) }

Identifiers

Naming

User-defined identifiers, such as variables, constants, and function names, must begin with either an underscore or an ASCII letter. These identifiers can then include underscores, ASCII letters, and digits.

Examples:

const thisIsAValidIdentifier0 = 0 const this_is_also_valid = 0 const this_1_is_2_also__3_valid = 0

However, identifiers cannot start with two underscores, as this prefix is reserved by the compiler.

// const __this_is_invalid = 0

Compiler Instructions

Additionally, some identifiers may begin with #, indicating a compiler directive or special function. These instructions have specific roles within the Swag programming environment. They evaluate at compile time.

Examples of compiler instructions:

#assert #run #main

Intrinsics

Identifiers that start with @ represent intrinsic functions. These functions are available at both compile time and runtime.

Examples of intrinsic functions:

@min() @max() @sqrt() @print()

Keywords

Special Keywords

Special keywords are predefined, reserved identifiers that have specific meanings within the Swag compiler. These cannot be used as identifiers in your code as they are integral to the language's structure.

if else elif while switch defer for foreach break fallthrough return case continue default and or orelse unreachable to until where in as true false null undefined using with cast dref retval try trycatch catch assume throw discard public internal private enum struct union impl interface func mtd namespace alias attr var let const moveref

Reserved Keywords

These keywords are reserved by the language and cannot be used by developers, even though they may not currently serve an active role in the syntax. They are reserved for potential future use or to avoid conflicts with language features.

is not do

Basic Types

These are the fundamental data types provided by the language. They are reserved keywords and form the core types that can be used to define variables, constants, and function return types.

s8 s16 s32 s64 u8 u16 u32 u64 f32 f64 bool string rune any typeinfo void code cstring cvarargs

Compiler Instructions

Compiler instructions are prefixed with # and are reserved for specific operations within the Swag compiler. These keywords are used to control various aspects of compilation and code generation. User-defined identifiers cannot start with #, ensuring that compiler keywords do not conflict with user-defined names.

#run #ast #test #init #drop #main #premain #message #dependencies #global #load #foreignlib #assert #print #error #warning #import #placeholder #if #else #elif #inject #macro #scope #defined #offsetof #alignof #sizeof #typeof #stringof #nameof #isconstexpr #location #decltype #gettag #hastag #runes #safety #include #cfg #os #arch #cpu #backend #module #file #line #self #curlocation #callerlocation #callerfunction #swagversion #swagrevision #swagbuildnum #swagos #code #type #up #index #alias0 #alias1 #alias2 // and more generally #aliasN #mix0 #mix1 #mix2 // and more generally #mixinN

Miscellaneous Intrinsics

Intrinsic functions are prefixed with @ and provide low-level operations that are often directly supported by the compiler or underlying hardware. These keywords offer specialized functionality that can be used during both compile time and runtime, depending on the context.

@spread @kindof @countof @dataof @mkslice @mkstring @mkcallback @mkany @mkinterface @err @dbgalloc @sysalloc @args @stringcmp @rtflags @typecmp @is @as @getcontext @pinfos @isbytecode @compiler @modules @gvtd @itftableof @assert @breakpoint @init @drop @postcopy @postmove @compilererror @compilerwarning @panic @print @setcontext

Intrinsics from libc

These intrinsic functions provide access to standard libc functionalities, allowing developers to perform common mathematical, memory, and string operations. They are also prefixed with @ to avoid conflicts with user-defined identifiers.

@abs @acos @asin @atan @atan2 @alloc @atomadd @atomand @atomcmpxchg @atomor @atomxchg @atomxor @bitcountlz @bitcountnz @bitcounttz @byteswap @cvaarg @cvaend @cvastart @ceil @cos @cosh @exp @exp2 @floor @free @log @log10 @log2 @max @memcmp @memcpy @memmove @memset @min @muladd @pow @realloc @rol @ror @round @sin @sinh @sqrt @strcmp @strlen @tan @tanh @trunc

Modifiers

Modifiers can be applied to specific keywords or operators to alter their behavior. These modifiers allow developers to fine-tune operations, giving more control over how certain code constructs are executed.

#prom #over #nodrop #move #moveraw #back #ref #constref

Fundamentals

Basic types

Signed Integers

Swag provides various signed integer types: s8, s16, s32, and s64. These types represent signed integers with different bit widths, allowing for both positive and negative values within their respective ranges. Each type is specifically designed to efficiently handle integer operations at varying levels of precision.

#test { let a: s8 = -1 // 8-bit signed integer, range: -128 to 127 let b: s16 = -2 // 16-bit signed integer, range: -32,768 to 32,767 let c: s32 = -3 // 32-bit signed integer, range: -2^31 to 2^31-1 let d: s64 = -4 // 64-bit signed integer, range: -2^63 to 2^63-1 @assert(a == -1) // Verifies that 'a' holds the value -1. @assert(b == -2) // Verifies that 'b' holds the value -2. @assert(c == -3) // Verifies that 'c' holds the value -3. @assert(d == -4) // Verifies that 'd' holds the value -4. @assert(#sizeof(a) == 1) // 'a' is an s8, so its size is 1 byte. @assert(#sizeof(b) == 2) // 'b' is an s16, so its size is 2 bytes. @assert(#sizeof(c) == 4) // 'c' is an s32, so its size is 4 bytes. @assert(#sizeof(d) == 8) // 'd' is an s64, so its size is 8 bytes. }

Unsigned Integers

Swag also supports various unsigned integer types: u8, u16, u32, and u64. These types represent unsigned integers, which can only hold non-negative values, making them ideal for scenarios where negative numbers are not applicable.

#test { let a: u8 = 1 // 8-bit unsigned integer, range: 0 to 255 let b: u16 = 2 // 16-bit unsigned integer, range: 0 to 65,535 let c: u32 = 3 // 32-bit unsigned integer, range: 0 to 2^32-1 let d: u64 = 4 // 64-bit unsigned integer, range: 0 to 2^64-1 @assert(a == 1) // Verifies that 'a' holds the value 1. @assert(b == 2) // Verifies that 'b' holds the value 2. @assert(c == 3) // Verifies that 'c' holds the value 3. @assert(d == 4) // Verifies that 'd' holds the value 4. @assert(#sizeof(a) == 1) // 'a' is a u8, so its size is 1 byte. @assert(#sizeof(b) == 2) // 'b' is a u16, so its size is 2 bytes. @assert(#sizeof(c) == 4) // 'c' is a u32, so its size is 4 bytes. @assert(#sizeof(d) == 8) // 'd' is a u64, so its size is 8 bytes. }

Floating-Point Types

Swag supports floating-point types f32 and f64. These types represent single-precision and double-precision floating-point numbers, respectively. They are crucial for calculations requiring fractional values and greater precision.

#test { let a: f32 = 3.14 // 32-bit floating-point value (single-precision) let b: f64 = 3.14159 // 64-bit floating-point value (double-precision) @assert(a == 3.14) // Verifies that 'a' holds the value 3.14. @assert(b == 3.14159) // Verifies that 'b' holds the value 3.14159. @assert(#sizeof(a) == 4) // 'a' is an f32, so its size is 4 bytes. @assert(#sizeof(b) == 8) // 'b' is an f64, so its size is 8 bytes. }

Boolean Type

The boolean type bool is used to represent true or false values. In Swag, a boolean is stored as a 1-byte value, offering a straightforward way to handle binary logic within your programs.

#test { let a: bool = true // Boolean value, representing 'true' let b: bool = false // Boolean value, representing 'false' @assert(a == true) // Verifies that 'a' is true. @assert(b == false) // Verifies that 'b' is false. @assert(#sizeof(a) == 1) // The size of a boolean is 1 byte. @assert(#sizeof(b) == 1) // The size of a boolean is 1 byte. }

String Type

The string type represents text. In Swag, strings are UTF-8 encoded and are stored as two 64-bit values: one for the pointer to the data and one for the length in bytes. Strings are designed to handle text efficiently, including international characters, while ensuring compatibility with common text processing routines.

#test { let a: string = "string 是" // A string containing UTF-8 encoded characters @assert(a == "string 是") // Verifies that 'a' holds the correct string. @assert(#sizeof(a) == 2 * #sizeof(*void)) // A string is stored as two 64-bit values (pointer and length). }

Rune Type

The rune type represents a 32-bit Unicode code point. It is used to store individual Unicode characters, allowing for the manipulation of text at the character level, supporting a wide range of languages and symbols.

#test { let a: rune = '是' // A single Unicode character, represented by a 32-bit code point @assert(a == '是') // Verifies that 'a' holds the correct Unicode code point. @assert(#sizeof(a) == 4) // The size of a rune is 4 bytes (32 bits). }
Note

Swag supports type reflection both at compile time and at runtime. This powerful feature allows for the inspection and manipulation of types dynamically, enabling more flexible and introspective programming paradigms. Further details on how to leverage type reflection will be explored in subsequent sections.

Type Creation with #decltype

You can use #decltype to create a type based on an expression. This is useful for cases where you want to infer or mirror the type of a variable dynamically, promoting code reusability and reducing redundancy.

#test { let a = 0 // The type of 'a' is inferred to be 's32'. let b: #decltype(a) = 1 // 'b' is declared with the same type as 'a' (which is 's32'). @assert(#typeof(a) == #typeof(b)) // Verifies that 'a' and 'b' have the same type. @assert(#typeof(a) == s32) // Verifies that the type of 'a' is 's32'. #assert #typeof(a) == #typeof(b) // Compile-time validation using '#assert'. #assert #typeof(a) == s32 }

Number literals

Number Representations

Integers can be written in multiple formats: decimal, hexadecimal, or binary. These different representations allow you to express numbers in the format that best suits your needs.

#test { const a: u32 = 123456 // Decimal format representation of the integer const b: u32 = 0xFFFF // Hexadecimal format, prefixed with '0x' (representing 65535) const c: u32 = 0b00001111 // Binary format, prefixed with '0b' (representing 15) @assert(a == 123456) // Verifies that 'a' holds the correct decimal value @assert(b == 65535) // Verifies that 'b' holds the correct hexadecimal value @assert(c == 15) // Verifies that 'c' holds the correct binary value }

Digit Separators

Numeric literals can be more readable by using the _ character as a digit separator. This is particularly useful for large numbers, without affecting their value.

#test { const a: u32 = 123_456 // Decimal with digit separators for better readability const b: u32 = 0xF_F_F_F // Hexadecimal with digit separators const c: u32 = 0b0000_1111 // Binary with digit separators @assert(a == 123456) // Verifies that 'a' holds the correct value @assert(b == 65535) // Verifies that 'b' holds the correct value @assert(c == 15) // Verifies that 'c' holds the correct value }

Default Integer Types

In Swag, hexadecimal or binary numbers default to u32 if they fit within 32 bits. If the value exceeds 32 bits, it is automatically inferred as u64.

#test { const a = 0xFF // Compiler infers 'a' as 'u32' since value fits within 32 bits #assert #typeof(a) == u32 // Verifies that 'a' is of type 'u32' const b = 0xF_FFFFF_FFFFFF // Large value exceeding 32 bits; inferred as 'u64' #assert #typeof(b) == u64 // Verifies that 'b' is of type 'u64' const c = 0b00000001 // Binary value within 32 bits, inferred as 'u32' #assert #typeof(c) == u32 // Verifies that 'c' is of type 'u32' const d = 0b00000001_00000001_00000001_00000001_00000001 // Exceeds 32 bits, inferred as 'u64' #assert #typeof(d) == u64 // Verifies that 'd' is of type 'u64' }

Booleans

A boolean type can be either true or false. As constants are known at compile time, you can use #assert to verify their values during compilation.

#test { const a = true #assert a == true // Compile-time check that 'a' holds the value true const b, c = false #assert b == false // Compile-time check that 'b' holds the value false #assert c == false // Compile-time check that 'c' holds the value false }

Floating Point Values

Floating point values use the standard C/C++ notation for floating-point literals. This provides familiarity and ease of use for developers.

#test { let a = 1.5 @assert(a == 1.5) // Verifies that 'a' holds the value 1.5 #assert #typeof(a) == f32 // The type of 'a' is inferred to be 'f32' let b = 0.11 @assert(b == 0.11) // Verifies that 'b' holds the value 0.11 let c = 15e2 @assert(c == 15e2) // Verifies that 'c' holds the value 15e2 (1500) let d = 15e+2 @assert(d == 15e2) // Verifies that 'd' holds the same value as 'c' let e = -1E-1 @assert(e == -0.1) // Verifies that 'e' holds the value -0.1 }

Default Floating Point Type

By default, floating-point literals are of type f32. This is different from languages like C/C++, where floating-point literals default to double (f64).

#test { let a = 1.5 @assert(a == 1.5) // Verifies that 'a' holds the value 1.5 #assert #typeof(a) == f32 // Verifies that 'a' is of type 'f32' #assert #typeof(a) != f64 // Verifies that 'a' is not of type 'f64' }

Literal Suffix

To specify the type of a literal explicitly, you can add a suffix to the literal. This is useful when a specific type is required, such as f64 or u8.

#test { let a = 1.5'f64 // Explicitly declare 'a' as 'f64' with the value 1.5 @assert(a == 1.5) // Verifies that 'a' holds the value 1.5 @assert(a == 1.5'f64) // Verifies that 'a' explicitly holds the value 1.5 as 'f64' #assert #typeof(a) == f64 // Verifies that 'a' is of type 'f64' let b = 10'u8 // Declare 'b' as 'u8' and assign it the value 10 @assert(b == 10) // Verifies that 'b' holds the value 10 #assert #typeof(b) == u8 // Verifies that 'b' is of type 'u8' let c = 1'u32 #assert #typeof(c) == u32 // Verifies that 'c' is of type 'u32' }

String

UTF-8 Encoding

Swag uses UTF-8 encoding for strings, which allows representation of a wide range of characters, including those from various languages and symbol sets.

String Comparison

Strings can be compared directly for equality using the == operator.

#test { const a = "this is a Chinese character: 是" // String with a Chinese character #assert a == "this is a Chinese character: 是" // Assert equality between two identical strings const b = "these are some Cyrillic characters: ӜИ" // String with Cyrillic characters #assert b == "these are some Cyrillic characters: ӜИ" // Assert equality for another string }

The rune Type

A rune represents a Unicode code point and is stored as a 32-bit value, ensuring it can accommodate any Unicode character.

#test { const a: rune = '是' // Define a rune using a Chinese character #assert a == '是' // Assert the rune matches the expected value #assert #sizeof(a) == #sizeof(u32) // Confirm the size of a rune is 32 bits (same as u32) }
Warning

Direct indexing of a string to retrieve a rune is not possible, except for ASCII strings. This is because Swag avoids the runtime overhead of UTF-8 decoding in string operations. However, the Std.Core module provides utilities for working with UTF-8 encoded strings.

String Indexing

When indexing a string, Swag returns a byte (u8), not a rune, which reflects the underlying UTF-8 encoding.

#test { const a = "this is a Chinese character: 是" // Define a string with mixed characters // Retrieves the first byte of the string, which corresponds to the character 't' const b = a[0] #assert b == 't' // Assert the first byte is 't' #assert #typeof(b) == #typeof(u8) // Confirm the type is `u8` (byte) // UTF-8 encoding affects indexing. 'X' is not at index 1 due to the multibyte encoding of the // preceding Chinese character. const c = "是X是" #assert c[1] != 'X' // Assert that the byte at index 1 is not 'X' }

String Concatenation

Swag allows compile-time concatenation of strings and other values using the ++ operator.

#test { const a = "the devil's number is " ++ 666 // Concatenation of a string with a number #assert a == "the devil's number is 666" // Assert the resulting string is as expected const b = 666 let c = "the devil's number is not " ++ (b + 1) ++ "!" @assert(c == "the devil's number is not 667!") // Assert the concatenated string after arithmetic let d = "there are " ++ 4 ++ " apples in " ++ (2 * 2) ++ " baskets" @assert(d == "there are 4 apples in 4 baskets") // Concatenation with a mix of literals and expressions }

Null Strings

A string can be null if it has not been initialized. This behavior can be used to check whether a string has been assigned a value.

#test { var a: string // Declare a string variable without initialization @assert(a == null) // Initially, the string is null a = "not null" // Assign a value to the string @assert(a != null) // Assert that the string is no longer null a = null // Set the string back to null @assert(a == null) // Confirm the string is null again }

Character Literals

Character literals are enclosed in quotes. These literals can represent any Unicode character, not just ASCII characters.

Note

A character literal's quotation mark must be preceded by a symbol or a space to avoid confusion with a type suffix.

#test { let char0 = 'a' // ASCII character literal let char1 = '我' // Unicode character literal let value = 5's32 // This is a quote for a type suffix }

Default Type of Character Literals

A character literal is a 32-bit integer by default. It can be assigned to any integer type or a rune, provided the value fits within the target type.

#test { { let a: u8 = 'a' // Assign character literal to an 8-bit unsigned integer let b: u16 = 'a' // Assign to a 16-bit unsigned integer let c: u32 = '我' // Assign to a 32-bit unsigned integer let d: u64 = '我' // Assign to a 64-bit unsigned integer let e: rune = '我' // Assign to a rune (32-bit) } { let a: s8 = 'a' // Assign to an 8-bit signed integer let b: s16 = 'a' // Assign to a 16-bit signed integer let c: s32 = '我' // Assign to a 32-bit signed integer let d: s64 = '我' // Assign to a 64-bit signed integer } }

Specifying Character Literal Types

Swag allows you to specify the type of a character literal using a suffix. This is useful for controlling the storage size of the character.

#test { let a = '0''u8 // Character literal explicitly typed as u8 @assert(a == 48) // ASCII value of '0' is 48 @assert(#typeof(a) == u8) // Confirm type is u8 let b = '1''u16 // Character literal explicitly typed as u16 @assert(b == 49) // ASCII value of '1' is 49 @assert(#typeof(b) == u16) // Confirm type is u16 let c = '2''u32 // Character literal explicitly typed as u32 @assert(c == 50) // ASCII value of '2' is 50 @assert(#typeof(c) == u32) // Confirm type is u32 let d = '3''u64 // Character literal explicitly typed as u64 @assert(d == 51) // ASCII value of '3' is 51 @assert(#typeof(d) == u64) // Confirm type is u64 let e = '4''rune // Character literal explicitly typed as rune @assert(e == 52) // ASCII value of '4' is 52 @assert(#typeof(e) == rune) // Confirm type is rune }

Escape Sequences

Swag supports escape sequences in strings and character literals, allowing special characters to be represented within these literals.

An escape sequence starts with a backslash ` and is followed by a specific character.

#test { const a = "this is ASCII code 0x00: \0" // Null character escape sequence const b = "this is ASCII code 0x07: \a" // Bell (alert) escape sequence const c = "this is ASCII code 0x08: \b" // Backspace escape sequence const d = "this is ASCII code 0x09: \t" // Horizontal tab escape sequence const e = "this is ASCII code 0x0A: \n" // Line feed escape sequence const f = "this is ASCII code 0x0B: \v" // Vertical tab escape sequence const g = "this is ASCII code 0x0C: \f" // Form feed escape sequence const h = "this is ASCII code 0x0D: \r" // Carriage return escape sequence const i = "this is ASCII code 0x22: \"" // Double quote escape sequence const j = "this is ASCII code 0x27: \'" // Single quote escape sequence const k = "this is ASCII code 0x5C: \\" // Backslash escape sequence }

ASCII and Unicode Escape Sequences

Escape sequences can also be used to specify characters via their ASCII or Unicode values.

#test { { const a = "\x26" // 1-byte hexadecimal ASCII escape sequence const b = "\u2626" // 2-byte hexadecimal Unicode 16-bit escape sequence const c = "\U00101234" // 4-byte hexadecimal Unicode 32-bit escape sequence } { const d = "\u2F46\u2F46" // Unicode escape sequences for two identical characters #assert d == "⽆⽆" // Assert the resulting string matches expected characters const e = '\u2F46' // Unicode escape sequence as a rune literal #assert e == '⽆' // Assert the rune matches the expected character } }

Raw Strings

A raw string is a string where escape sequences and special characters are not processed. This can be useful for strings that contain many special characters or backslashes.

A raw string is enclosed within # characters, which ensures that its content is taken literally.

#test { const a = #"\u2F46"# // Raw string containing a Unicode escape sequence #assert a != "⽆" // Raw string does not interpret the escape sequence #assert a == #"\u2F46"# // Raw string content matches the literal input }

These two strings are equivalent, even though one uses escape sequences and the other is raw:

#test { const a = "\\hello \\world" // String with escape sequences const b = #"\hello \world"# // Equivalent raw string #assert a == b // Assert both strings are identical }

Multiline Raw Strings

Raw strings can span multiple lines, and all leading spaces before the closing "# are removed from each line.

#test { const a = #"this is a string "# }

In the above example, the multiline raw string retains its formatting, except that leading spaces before the closing "# are stripped from each line.

#test { // The resulting string is: // this is // a // string }

Multiline Strings

Multiline strings start and end with """. Unlike raw strings, they still process escape sequences.

#test { const a = """this is a string """ // Equivalent to: // this is // a // string }

In a multiline or raw string, ending a line with ` prevents the following end of line (EOL) from being included in the string.

#test { const a = """\ this is a string """ // The resulting string is: // this is // a // string }

#stringof and #nameof Intrinsics

The #stringof intrinsic returns the string representation of a constant expression at compile time.

#test { const X = 1 #assert #stringof(X) == "1" // The string representation of X is "1" #assert #stringof(X + 10) == "11" // Expression evaluation is also possible }

The #nameof intrinsic returns the name of a variable, function, etc., as a string.

#test { const X = 1 #assert #nameof(X) == "X" // Returns the variable name "X" }

Constants

Constants with const

Using const implies that the value must be known by the compiler at compile time. This ensures that the value is embedded directly into the compiled code, eliminating any runtime memory usage for simple types like integers or strings. Essentially, the compiler replaces occurrences of these constants with their respective values wherever they are referenced in the code, leading to more optimized and efficient code execution.

#test { // These are immutable constants. Once declared, they are fixed and cannot be altered. const a = 666 // 'a' is an integer constant known and resolved at compile time. #assert a == 666 // Assertion to verify the value of 'a'. const b: string = "string" // 'b' is a string constant known and resolved at compile time. #assert b == "string" // Assertion to verify the value of 'b'. }

Constants with Complex Types

Swag also supports constants with more complex data types, such as arrays or structures. When declaring constants with these types, the data is stored in the program's data segment, which incurs a memory footprint. Additionally, you can obtain the memory address of these constants at runtime, allowing you to manipulate their values indirectly through pointers.

Static Arrays

The following example demonstrates a static array, which is an array with a fixed size. This array contains three elements of type s32, a signed 32-bit integer.

#test { const a: [3] s32 = [0, 1, 2] // 'a' is a constant static array with 3 signed 32-bit integers. let ptr = &a[0] // Obtain the memory address of the first element of the array. @assert(ptr[0] == 0) // Verify the first element via pointer dereferencing. @assert(ptr[2] == 2) // Verify the third element via pointer dereferencing. // Compile-time assertions to ensure the array's contents are as expected. #assert a[0] == 0 // Verify the first element of the array at compile time. #assert a[1] == 1 // Verify the second element of the array at compile time. #assert a[2] == 2 // Verify the third element of the array at compile time. }
Multidimensional Arrays

This example illustrates a multidimensional array declared as a constant. The array is a 4x4 matrix of 32-bit floating-point numbers (f32). We will explore arrays in further detail later in this documentation.

#test { const M4x4: [4, 4] f32 = [ // 'M4x4' is a constant 4x4 matrix of 32-bit floating-point numbers. [1, 0, 0, 0], // First row of the matrix. [0, 1, 0, 0], // Second row of the matrix. [0, 0, 1, 0], // Third row of the matrix. [0, 0, 0, 1] // Fourth row of the matrix. ] }

Key Difference Between let and const

The primary distinction between let and const lies in the timing of value determination. The value assigned to a const must be determined at compile time, meaning it is fixed and known before the program runs. Conversely, a let allows for dynamic assignment, where the value can be computed and assigned during runtime. Despite this, both let and const ensure that their values are assigned only once, maintaining immutability.

Variables

Variable Declaration

Variables are declared using the let or var keyword, followed by a : and the variable's type.

  • let: Used for declaring a constant variable. The value assigned cannot be modified, ensuring immutability.
  • var: Used for declaring a mutable variable. The value assigned can be modified after initialization.
#test { // 'a' is a constant variable of type 'u32', initialized with the value 1. // It is immutable, meaning its value cannot be altered after assignment. let a: u32 = 1 @assert(a == 1) // 'b' is a constant variable of type 'string', initialized with the value "string". let b: string = "string" @assert(b == "string") // 'c' is a mutable variable of type 's32'. Its value is initialized to 42 and can be modified later. var c: s32 = 42 c += 1 @assert(c == 43) // Verifies that 'c' has been correctly incremented to 43. }

Multiple Variable Declarations

Swag allows the declaration of multiple variables of the same type on a single line.

#test { let a, b: u32 = 123 // Both 'a' and 'b' are of type 'u32', initialized with the value 123. @assert(a == 123) @assert(b == 123) }

Alternatively, multiple variables of different types can also be declared on the same line.

#test { let a: u32 = 12, b: f32 = 1.5 // 'a' is of type 'u32', and 'b' is of type 'f32'. @assert(a == 12) @assert(b == 1.5) }

Default Initialization

If a variable is declared without an initial value, it is automatically initialized with its default value. A variable is always initialized, ensuring it never holds an undefined value.

#test { var a: bool @assert(a == false) // The default value for a boolean is 'false'. var b: string @assert(b == null) // The default value for a string is 'null'. var c: f64 @assert(c == 0) // The default value for a floating-point number is 0. }

Uninitialized Variables

If you want a variable to remain uninitialized and avoid the default initialization cost, you can assign it undefined. This approach should be used with caution, as the variable will be in an undefined state.

#test { var a: bool = undefined // 'a' is intentionally left uninitialized. var b: string = undefined // 'b' is intentionally left uninitialized. }

Type Inference

Swag supports type inference, where the type of a variable can be automatically deduced from its assigned value. This allows the omission of explicit type annotations in many cases.

Below are examples demonstrating type inference.

#test { let a = 1.5 // The type of 'a' is inferred to be 'f32' due to the floating-point literal. @assert(a == 1.5) #assert #typeof(a) == f32 let b = "string" // The type of 'b' is inferred to be 'string'. @assert(b == "string") #assert #typeof(b) == string let c = 1.5'f64 // The type of 'c' is explicitly set to 'f64' using a suffix. @assert(c == 1.5) #assert #typeof(c) == f64 }

Type inference also applies when declaring multiple variables simultaneously.

#test { let a, b = true // Both 'a' and 'b' are inferred to be of type 'bool'. @assert(a == true) @assert(b == true) #assert #typeof(a) == #typeof(true) #assert #typeof(b) == #typeof(a) let c = 1.5, d = "string" // 'c' is inferred as 'f32', and 'd' as 'string'. @assert(c == 1.5) @assert(d == "string") #assert #typeof(c) == f32 #assert #typeof(d) == string }

Special Variables

Swag offers special keywords and attributes to manage variable storage and behavior beyond typical usage.

Thread-Local Storage

By tagging a global variable with #[Swag.Tls], you can store it in thread-local storage. Each thread will then have its own independent copy of the variable.

#[Swag.Tls] var G = 0 // 'G' is a global variable stored in thread-local storage.
Global Variables

A local variable can be tagged with #[Swag.Global] to make it global, functioning similarly to the static keyword in C/C++.

#test { func toto() -> s32 { #[Swag.Global] var G1 = 0 // 'G1' is a static-like variable that retains its value across function calls. G1 += 1 return G1 } @assert(toto() == 1) // First call increments G1 to 1. @assert(toto() == 2) // Second call increments G1 to 2. @assert(toto() == 3) // Third call increments G1 to 3. }
Compile-Time Variables

Global variables marked with #[Swag.Compiler] are only accessible during compile-time and are excluded from the runtime code.

#[Swag.Compiler] var G2 = 0 // 'G2' is a compile-time-only variable. #run { G2 += 5 // This increment occurs at compile-time. }

Operators

Arithmetic Operators

These operators perform basic arithmetic operations such as addition, subtraction, multiplication, and division. Modulus operation is also supported to get the remainder of a division.

#test { var x: s32 = 10 // Addition: Increments x by 1 x = x + 1 // Subtraction: Decrements x by 1 x = x - 1 // Multiplication: Multiplies x by 2 x = x * 2 // Division: Divides x by 2 x = x / 2 // Modulus: Finds remainder when x is divided by 2 x = x % 2 }

Bitwise Operators

Bitwise operators manipulate individual bits of integral types. These operators perform operations such as XOR, AND, OR, and bit shifting.

#test { var x: s32 = 10 // XOR: Bitwise XOR with 2 x = x ^ 2 // Bitwise AND: Applies a bitmask to retain only the least significant bit x = x & 0b0000_0001's32 // Bitwise OR: Sets the least significant bit to 1 x = x | cast(s32) 0b0000_0001 // Shift Left: Shifts bits to the left by 1 position x = x << 1 // Shift Right: Shifts bits to the right by 1 position x = x >> 1 }

Assignment Operators

These operators perform the basic arithmetic or bitwise operation and assign the result directly to the left operand. The affect versions of these operators make the code concise and clear.

#test { var x: s32 = 10 x += 1 // Addition assignment x -= 1 // Subtraction assignment x *= 2 // Multiplication assignment x /= 2 // Division assignment x %= 2 // Modulus assignment x ^= 2 // XOR assignment x |= 0b0000_0001 // Bitwise OR assignment x &= 0b0000_0001 // Bitwise AND assignment x <<= 1 // Shift left assignment x >>= 1 // Shift right assignment }

Unary Operators

Unary operators operate on a single operand. These include logical NOT, bitwise NOT, and negation.

#test { var x = true var y = 0b0000_0001'u8 var z = 1 // Invert boolean: Logical NOT x = !x // Invert bits: Bitwise NOT y = ~y // Negation: Unary minus z = -z @assert(z == -1) @assert(x == false) @assert(y == 0b1111_1110) }

Comparison Operators

Comparison operators compare two values and return a boolean result. They include equality, inequality, and relational operators.

#test { { var a = false // Equal: Checks if two values are equal a = 1 == 1 ? true : false // Not equal: Checks if two values are not equal a = 1 != 1 ? true : false // Less than or equal: Checks if the left value is less than or equal to the right value a = 1 <= 1 ? true : false // Greater than or equal: Checks if the left value is greater than or equal to the right value a = 1 >= 1 ? true : false // Less than: Checks if the left value is less than the right value a = 1 < 1 ? true : false // Greater than: Checks if the left value is greater than the right value a = 1 > 1 ? true : false } { let x = 5 let y = 10 @assert(x == 5) @assert(x != 10) @assert(x <= 5) @assert(x < 10) @assert(x >= 5) @assert(x > 0) } }

Logical Operators

Logical operators evaluate expressions to return a boolean value. Swag uses and and or instead of && and || found in C/C++.

#test { var a = false a = (1 > 10) and (10 < 1) // Logical AND a = (1 > 10) or (10 < 1) // Logical OR }

Ternary Operator

The ternary operator tests an expression and returns one of two values depending on the result of the test. The syntax is A = Expression ? B : C, where B is returned if the expression is true, and C is returned if the expression is false.

#test { // Returns 1 because the expression 'true' is... true. let x = true ? 1 : 666 @assert(x == 1) // Returns 666 because the expression 'x == 52' is false. let y = (x == 52) ? 1 : 666 @assert(y == 666) }

Spaceshift Operator

Operator <=> returns -1, 0, or 1 if the left expression is lower, equal, or greater than the right expression, respectively. The returned type is s32.

A <=> B == -1 if A < B A <=> B == 0 if A == B A <=> B == 1 if A > B
#test { { let a = -1.5 <=> 2.31 #assert #typeof(a) == s32 @assert(a == -1) @assert(-10 <=> 10 == -1) @assert(10 <=> -10 == 1) @assert(10 <=> 10 == 0) } { let x1 = 10 <=> 20 @assert(x1 == -1) let x2 = 20 <=> 10 @assert(x2 == 1) let x3 = 20 <=> 20 @assert(x3 == 0) } }

Null-Coalescing Operator

The operator orelse returns the left expression if it is not null, otherwise it returns the right expression.

Works with strings, pointers, and structures with the opData special function (covered later).

#test { var a = "string1" let b = "string2" // c = a if a is not null, else c = b. var c = a orelse b @assert(c == "string1") a = null c = a orelse b @assert(c == "string2") }

Works also for basic types like integers.

#test { let a = 0 let b = 1 let c = a orelse b @assert(c == b) }

Type Promotion

Unlike C, types are not promoted to 32 bits when dealing with 8 or 16-bit types. However, types will be promoted if the two sides of an operation do not have the same type.

#test { #assert #typeof(0'u8 + 1'u8) == u8 #assert #typeof(0'u8 + 1'u16) == u16 // Priority to bigger type #assert #typeof(0'u8 + 1'u32) == u32 #assert #typeof(0'u8 + 1'u64) == u64 #assert #typeof(0'u8 + 1's8) == s8 // Priority to signed type #assert #typeof(0'u8 + 1's16) == s16 #assert #typeof(0'u8 + 1's32) == s32 #assert #typeof(0'u8 + 1's64) == s64 #assert #typeof(0'u8 + 1'f32) == f32 #assert #typeof(0'u8 + 1'f64) == f64 #assert #typeof(0's8 + 1'u16) == u16 // Priority to bigger type also }

This means that an 8/16-bit operation (like an addition) can more easily overflow if you do not take care. In that case, you can use the #prom modifier on the operation, which will promote the type to at least 32 bits. The operation will be done accordingly.

#test { #assert #typeof(255'u8 + #prom 1'u8) == u32 #assert 255'u8 + #prom 1'u8 == 256 // No overflow, because the operation is done in 32 bits. }

We'll see later how Swag deals with that kind of overflow, and more generally, with safety.

Operator Precedence

~ * / % + - >> << & | ^ <=> == != < <= > >= and or

If two operators have the same precedence, the expression is evaluated from left to right.

#test { // Multiplication before addition @assert(10 + 2 * 3 == 16) // Parentheses change precedence @assert((10 + 2) * 3 == 36) // Addition and subtraction before comparison @assert((5 + 3 < 10 - 2) == false) // 'and' before 'or' @assert((false and false or true) == true) // '<<' before '&' @assert((10 & 2 << 1) == 0) @assert(((10 & 2) << 1) == 4) }

Cast

Explicit Cast with cast

Explicit casting is necessary when you need to convert a value from one type to another manually. This can be achieved using the cast(type) value syntax, which transforms the value into the specified type.

#test { // 'x' is initialized as a floating-point number (f32 by default) let x = 1.0 #assert #typeof(x) == f32 // 'y' is explicitly cast to a 32-bit signed integer (s32) let y = cast(s32) x #assert #typeof(y) == s32 @assert(y == 1) // The floating-point value 1.0 is cast to the integer value 1 }

Automatic Cast with cast()

A cast() without an expression stands for automatic cast, allowing the compiler to automatically determine and perform the cast to match the type on the left-hand side of the assignment.

#test { let x: f32 = 1.0 let y: s32 = cast() x // Automatically cast 'x' to 's32' #assert #typeof(y) == s32 @assert(y == 1) }

This also works for a function call argument.

func testAutoCast(x: s32) { @assert(x == 1) } #test { testAutoCast(cast() 1.4) // Automatic cast to the type 's32' of 'x' }

Bitcast

The bit cast mode enables bit-level reinterpretation of a value's type without altering the underlying bit pattern. This operation, known as a bitcast, is only valid when the source and destination types share the same size.

#test { let x: f32 = 1.0 let y: u32 = cast<bit>(u32) x // Reinterpret the bits of 'x' as a 'u32' @assert(y == 0x3f800000) // 1.0 in IEEE 754 floating-point format equals 0x3f800000 in hex // Casting back to the original type should yield the original value #assert cast<bit>(u32) 1.0 == 0x3f800000 #assert cast<bit>(f32) 0x3f800000 == 1.0 // Reinterpreting the bits back to 'f32' gives 1.0 }

This example demonstrates the reverse operation, where an integer representing a bit pattern is reinterpreted as a floating-point number.

#test { let rawBits: u32 = 0x40490FDB // Hexadecimal representation of the float 3.1415927 let pi: f32 = cast<bit>(f32) rawBits // Interpret the bit pattern as a floating-point number @assert(pi == 3.1415927) // This now represents the value of pi as a floating-point number // Verifying that casting back to the original bit pattern restores the initial value let backToBits: u32 = cast<bit>(u32) pi @assert(backToBits == 0x40490FDB) }

Implicit Casts

Swag allows automatic type conversions, known as implicit casts, when assigning a value of one type to a variable of another type. This occurs without requiring an explicit cast statement. However, implicit casts are only permitted when there is no risk of precision loss or range overflow, ensuring that the value remains intact and no data is lost during the conversion.

Implicit Cast Rules
  1. Widening Conversions: Implicit casts are allowed when converting a smaller type to a larger type, such as from s8 to s16, or from f32 to f64. These conversions are safe because the larger type can represent all possible values of the smaller type.
  2. Sign Preservation: When implicitly casting between signed and unsigned types, Swag ensures that no data loss occurs by verifying that the value can be represented in both types. Implicit casts from unsigned to signed types (and vice versa) are only allowed when the value is positive and within the range of the target type.
  3. No Implicit Narrowing: Swag does not permit implicit casts that could potentially lose data or precision, such as from s32 to s8. These narrowing conversions require an explicit cast to indicate that the developer is aware of the potential data loss.
Examples of Implicit Casts

Let's explore some examples to illustrate these rules.

#test { // Implicit cast from 8-bit signed integer (s8) to 16-bit signed integer (s16) let x: s16 = 1's8 // Safe conversion, no loss of precision // Implicit cast from 16-bit signed integer (s16) to 32-bit signed integer (s32) let y: s32 = 1's16 // Safe conversion, no loss of precision // Implicit cast from 32-bit signed integer (s32) to 64-bit signed integer (s64) let z: s64 = 1's32 // Safe conversion, no loss of precision // Implicit cast from 8-bit unsigned integer (u8) to 16-bit unsigned integer (u16) let a: u16 = 255'u8 // Safe conversion, no loss of precision // Implicit cast from 16-bit unsigned integer (u16) to 32-bit unsigned integer (u32) let b: u32 = 65535'u16 // Safe conversion, no loss of precision // Implicit cast from 32-bit unsigned integer (u32) to 64-bit unsigned integer (u64) let c: u64 = 4294967295'u32 // Safe conversion, no loss of precision // Implicit cast from 32-bit floating-point (f32) to 64-bit floating-point (f64) let d: f64 = 1.23'f32 // Safe conversion, f64 can represent all f32 values accurately }
Examples Where Implicit Casts Are Not Allowed

There are cases where implicit casts are not permitted due to the risk of data loss or precision issues. In such situations, Swag requires an explicit cast to ensure that the developer is aware of and accepts the risks.

Additionally, the cast mode unsafe can be used in explicit casts to indicate that the value may lose some precision without raising an error.

#test { // Implicit cast from 16-bit signed integer (s16) to 8-bit signed integer (s8) // This would generate a compilation error because s8 cannot represent all s16 values. // Uncommenting the following lines would cause an error: // let z0: s16 = 100 // let z1: s8 = z0 // Error: Implicit cast from 's16' to 's8' is not allowed // To perform this cast, an explicit cast is required: let z0: s16 = 256 let z1: s8 = cast<overflow>(s8) z0 // Explicit cast needed to convert from s16 to s8 @assert(z1 == 0) // This may cause data loss as 256 cannot be represented in s8 (max is 127) // Implicit cast from unsigned to signed type where the value is out of range let u_val: u16 = 65535 // let s_val: s16 = u_val // Error: Implicit cast from 'u16' to 's16' is not allowed due to potential data loss // To perform this cast, an explicit cast is required, with the risk of data loss: let s_val: s16 = cast<overflow>(s16) u_val // This could result in an unexpected negative value @assert(s_val == -1) // 65535 in u16 becomes -1 in s16 due to overflow // Implicit cast from f64 to f32, which can lose precision let large_float: f64 = 1.23456789012345611111 // let smaller_float: f32 = large_float // Error: Implicit cast from 'f64' to 'f32' is not allowed due to precision loss // Explicit cast is required when converting from f64 to f32 let smaller_float: f32 = cast(f32) large_float }

Alias

Type Alias

An alias allows you to create a shorthand for an existing type, making it possible to refer to that type using a new, potentially more descriptive name. This can enhance code clarity, reduce repetition, and make complex types easier to work with.

Basic Type Alias

Using alias, you can define an alias for an existing type. This alias can then be used in place of the original type, simplifying the code or improving its readability. It’s important to note that a type alias does not create a new type but merely provides an alternative name for an existing one.

#test { enum RGB { R, G, B } @assert(RGB.R == 0) alias Color = RGB // 'Color' is now a shorthand for 'RGB' @assert(Color.G == 1) // 'Color' can be used wherever 'RGB' is expected. }

Aliasing Primitive Types

You can create aliases for primitive types, making your code more readable and meaningful, especially when the alias represents a specific domain concept.

#test { alias Float32 = f32 // 'Float32' is an alias for 'f32' alias Float64 = f64 // 'Float64' is an alias for 'f64' var x: Float32 = 1.0 // Equivalent to declaring 'var x: f32' var y: Float64 = 1.0 // Equivalent to declaring 'var y: f64' #assert #typeof(Float32) == f32 #assert #typeof(Float64) == f64 }

Strict Type Alias

In cases where you need to enforce type safety, Swag provides the Swag.Strict attribute, which can be applied to a alias. This makes the alias a distinct type, preventing implicit casting between the alias and the original type. Explicit casting is still allowed, but the distinct type ensures that operations requiring type specificity are clear and deliberate.

#test { #[Swag.Strict] alias MyType = s32 // 'MyType' is a distinct type, not interchangeable with 's32' #assert #typeof(MyType) != s32 let x: MyType = cast(MyType) 0 // Explicit cast is required to assign a value let y: s32 = cast(s32) x // Casting back to 's32' also requires an explicit cast }

Name Alias

An alias allows you also to create an alternative name or shortcut for functions, variables, or namespaces. This can be particularly useful for simplifying code, managing long or complex names, or improving the readability and maintainability of your code.

Function Name Alias

With alias, you can define a shorter or more convenient name for a function. This is particularly helpful when dealing with functions that have long or descriptive names, allowing you to simplify your code without sacrificing functionality.

#test { func thisIsABigFunctionName(x: s32) => x * x alias myFunc = thisIsABigFunctionName @assert(myFunc(4) == 16) // 'myFunc' is now an alias for 'thisIsABigFunctionName' }

Variable and Namespace Alias

alias can also be used to alias variables and namespaces, offering a shorter or more convenient reference that can be used throughout your code. This is particularly useful in large codebases where certain variables or namespaces are frequently referenced.

#test { var myLongVariableName: s32 = 0 alias short = myLongVariableName short += 2 // 'short' is an alias for 'myLongVariableName' @assert(myLongVariableName == 2) // The original variable reflects changes made via the alias }

Data structures

Array

Static Arrays in Swag

Static arrays are fixed-size arrays where the size is known and set at compile time. Unlike dynamic arrays from the Std.Core module, static arrays do not change in size during runtime. They are ideal for situations where memory and size constraints are predictable and fixed.

Declaring a Static Array

A static array is declared using the syntax [N] followed by the type, where N is the number of elements.

#test { var array: [2] s32 // Declare a static array with two 32-bit signed integers (s32) array[0] = 1 // Assign the value 1 to the first element array[1] = 2 // Assign the value 2 to the second element }

Array Size and Memory

The @countof intrinsic can be used to get the number of elements in an array, while #sizeof provides the total size of the array in bytes.

#test { // Declare a static array with two 32-bit signed integer (s32) elements var array: [2] s32 // Use '#typeof' to get the array type and check the number of elements with '.count' #assert #typeof(array).count == 2 // '@countof' is a runtime intrinsic that also works with compile-time elements #assert @countof(array) == 2 // Ensure the array contains 2 elements // '#sizeof' is a compile-time intrinsic that returns the size in bytes of the given element #assert #sizeof(array) == 2 * #sizeof(s32) // Verify the total size in bytes }

Obtaining the Address of an Array

The @dataof intrinsic retrieves the address of the first element in an array.

#test { var array: [2] s32 // Declare a static array of two s32 elements var ptr0 = @dataof(array) // Get the base address of the array ptr0[0] = 1 // Access the first element via the pointer // Alternatively, obtain the address of a specific element var ptr1 = &array[0] // Get the address of the first element ptr1[1] = 2 // Access the second element via the pointer @assert(array[0] == 1) // Confirm that the first element is 1 @assert(array[1] == 2) // Confirm that the second element is 2 }

Array Literals

An array literal is a list of elements enclosed in square brackets [A, B, ...].

#test { var arr = [1, 2, 3, 4] // Declare and initialize a static array of four s32 elements #assert @countof(arr) == 4 // Verify the array contains 4 elements #assert #typeof(arr) == #type [4] s32 // Verify the array's type is [4] s32 }

Type Deduction in Arrays

Swag can deduce the size of the array from the initialization expression, eliminating the need to specify the dimension explicitly.

#test { // Array dimension is deduced from the initialization with 2 elements var array: [] s32 = [1, 2] @assert(array[0] == 1) @assert(array[1] == 2) #assert @countof(array) == 2 // Both dimensions and types are deduced from the initialization expression var array1 = ["10", "20", "30"] @assert(array1[0] == "10") @assert(array1[1] == "20") @assert(array1[2] == "30") #assert @countof(array1) == 3 }

Default Initialization

Static arrays in Swag are automatically initialized to zero values (0 for integers, null for strings, false for booleans, etc.) unless specified otherwise.

#test { var array: [2] s32 // Declare a static array of two s32 elements @assert(array[0] == 0) // Ensure the array is initialized to 0 @assert(array[1] == 0) }

Preventing Default Initialization

To enhance performance, you can bypass the default initialization by using undefined, particularly useful when you plan to manually initialize all array elements.

#test { var array: [100] s32 = undefined // Declare a large array without initializing it }

Constant Arrays

Static arrays with compile-time values can be declared as constants, meaning their values cannot be altered after declaration.

#test { const array = [1, 2, 3, 4] // Declare a constant static array #assert array[0] == 1 // Compile-time dereference of the first element #assert array[3] == 4 // Compile-time dereference of the fourth element }

Type Inference from Array Literals

If the array's type is not explicitly specified, Swag infers the type based on the first literal value, which then applies to all other elements in the array.

#test { var arr = [1'f64, 2, 3, 4] // All elements are inferred to be 'f64' #assert @countof(arr) == 4 // Verify the array has 4 elements #assert #typeof(arr) == #type [4] f64 // Confirm that all elements are of type f64 @assert(arr[3] == 4.0) // Confirm the value of the fourth element }

Multi-Dimensional Arrays

Swag supports the declaration of multi-dimensional arrays, allowing you to work with arrays with multiple dimensions.

The syntax for declaring multi-dimensional arrays is [X, Y, Z...], where X, Y, and Z represent each dimension.

#test { var array: [2, 2] s32 // Declare a 2x2 static array of s32 array[0, 0] = 1 // Assign the value 1 to the element at [0, 0] array[0, 1] = 2 // Assign the value 2 to the element at [0, 1] array[1, 0] = 3 // Assign the value 3 to the element at [1, 0] array[1, 1] = 4 // Assign the value 4 to the element at [1, 1] }

C/C++ Style Multi-Dimensional Arrays

Swag also allows for the declaration of multi-dimensional arrays using C/C++ syntax, where an array of arrays is created. This method is equivalent to Swag's native multi-dimensional array syntax.

#test { var array: [2] [2] s32 // Declare a 2x2 static array of s32 using C/C++ syntax array[0, 0] = 1 // Assign the value 1 to the element at [0, 0] array[0, 1] = 2 // Assign the value 2 to the element at [0, 1] array[1, 0] = 3 // Assign the value 3 to the element at [1, 0] array[1, 1] = 4 // Assign the value 4 to the element at [1, 1] }

Array Size Deduction

Swag can deduce the size of arrays, including multi-dimensional arrays, directly from the initialization expression.

#test { var array = [1, 2, 3, 4] // Size deduced to be 4 var array1 = [[1, 2], [3, 4]] // Size deduced to be 2x2 #assert @countof(array) == 4 // Verify that the size of the first array is 4 #assert @countof(array1) == 2 // Verify that the outer array has 2 elements }

Single Value Initialization

Swag allows the initialization of an entire array with a single value. This feature is available only for variables, not constants, and supports basic types such as integers, floats, strings, booleans, and runes.

#test { // Initialize the entire 2x2 boolean array with 'true' var arr: [2, 2] bool = true @assert(arr[0, 0] == true) @assert(arr[1, 1] == true) // Initialize the entire 5x10 string array with "string" var arr1: [5, 10] string = "string" @assert(arr1[0, 0] == "string") @assert(arr1[4, 9] == "string") }

Slice

Slices in Swag

A slice in Swag provides a dynamic view over a contiguous block of memory. Unlike static arrays, slices can be resized or point to different segments of memory at runtime. A slice is composed of a pointer to the data buffer and a u64 value that stores the number of elements in the slice.

This design makes slices a powerful tool for working with subsets of data without the need to copy it, enabling efficient manipulation of large datasets.

#test { var a: [..] bool // Declare a slice of boolean values #assert #sizeof(a) == #sizeof(*void) + #sizeof(u64) // Ensure slice size includes pointer and element count }

Initializing Slices

Slices can be initialized in a similar manner to arrays. By assigning an array literal to a slice, the slice will reference the elements in the array.

#test { var a: const [..] u32 = [10, 20, 30, 40, 50] // Initialize a slice with an array literal @assert(@countof(a) == 5) // Verify that the slice contains 5 elements @assert(a[0] == 10) @assert(a[4] == 50) // Slices are dynamic, allowing for modification of the referenced elements at runtime. a = [1, 2] // Reassign the slice to a different array @assert(@countof(a) == 2) @assert(a[0] == 1) @assert(a[1] == 2) }

Accessing Slice Data

At runtime, you can use @dataof to retrieve the address of the data buffer and @countof to get the number of elements in the slice.

#test { var a: const [..] u32 = [10, 20, 30, 40, 50] // Initialize a slice with an array literal let count = @countof(a) // Get the number of elements in the slice let addr = @dataof(a) // Get the address of the slice's data buffer @assert(count == 5) @assert(addr[0] == 10) @assert(addr[4] == 50) a = [1, 2] // Reassign the slice to a different array @assert(@countof(a) == 2) }

Creating Slices with @mkslice

The @mkslice function allows you to create a slice using your own pointer and element count.

#test { var array: [4] u32 = [10, 20, 30, 40] // Create a slice of 'array' starting at index 2, with 2 elements. let slice: [..] u32 = @mkslice(&array[0] + 2, 2) @assert(@countof(slice) == 2) @assert(slice[0] == 30) @assert(slice[1] == 40) @assert(array[2] == 30) slice[0] = 314 // Modify the original array via the slice @assert(array[2] == 314) }

Slicing Strings

For strings, the slice must be const because strings are immutable.

#test { let str = "string" let strSlice: const [..] u8 = @mkslice(@dataof(str), 2) @assert(strSlice[0] == 's') @assert(strSlice[1] == 't') }

Slicing with the .. Operator

You can create a slice using the .. operator instead of @mkslice. For instance, you can slice a string.

#test { let str = "string" // Create a slice starting at byte 0 and ending at byte 3. let slice = str[1..3] @assert(slice == "tri") }

Inclusive and Exclusive Slicing

By default, the upper limit in a slice is inclusive. To exclude the upper limit, use ..< instead of ...

#test { let str = "string" let slice = str[1..<3] @assert(slice == "tr") }

Slicing to the End

You can omit the upper bound to create a slice that extends to the end of the sequence.

#test { let str = "string" let slice = str[2..] @assert(slice == "ring") }

Slicing from the Start

To create a slice from the start (index 0), you can omit the lower bound.

#test { let str = "string" let slice = str[..2] // Index 2 is included @assert(slice == "str") let slice1 = str[..<2] // Index 2 is excluded @assert(slice1 == "st") }

Slicing Arrays

Arrays can also be sliced similarly to strings.

#test { let arr = [10, 20, 30, 40] let slice = arr[2..3] @assert(slice[0] == 30) @assert(slice[1] == 40) @assert(@countof(slice) == 2) // Create a slice for the entire array. let slice1 = arr[..] @assert(@countof(slice1) == @countof(arr)) }

Slicing a Slice

It is possible to create a slice from another slice.

#test { let arr = [10, 20, 30, 40] let slice1 = arr[1..3] @assert(slice1[0] == 20) @assert(slice1[1] == 30) @assert(slice1[2] == 40) let slice2 = slice1[1..2] @assert(slice2[0] == 30) @assert(slice2[1] == 40) }

Transforming a Pointer to a Slice

You can transform a pointer into a slice.

#test { var arr = [10, 20, 30, 40] let ptr = &arr[2] let slice = ptr[0..1] @assert(slice[0] == 30) @assert(slice[1] == 40) @assert(@countof(slice) == 2) }

Tuple

Tuples in Swag

In Swag, a tuple represents an anonymous structure, sometimes referred to as a struct literal. Tuples are particularly useful for grouping together elements of various types without requiring a formally defined structure. The syntax for creating a tuple involves enclosing the elements within curly braces {}.

#test { let tuple1 = {2, 2} // A tuple containing two integer elements. let tuple2 = {"string", 2, true} // A tuple with a string, an integer, and a boolean element. }

Accessing Tuple Values

By default, the values in a tuple are accessed using automatically generated field names. These names follow the pattern itemX, where X represents the zero-based index of the element within the tuple.

#test { let tuple = {"string", 2, true} // A tuple containing three elements. @assert(tuple.item0 == "string") // Accesses the first element (index 0). @assert(tuple.item1 == 2) // Accesses the second element (index 1). @assert(tuple.item2 == true) // Accesses the third element (index 2). }

Named Fields in Tuples

In Swag, it is possible to assign custom names to tuple fields, enhancing the readability and maintainability of the code. When custom names are assigned, the default itemX access pattern remains available, offering flexibility in how you interact with tuple elements.

#test { let tuple = {x: 1.0, y: 2.0} // A tuple with named fields 'x' and 'y'. @assert(tuple.x == 1.0) // Accesses the value of 'x' via its custom name. @assert(tuple.item0 == 1.0) // Accesses the value of 'x' using the default 'item0'. @assert(tuple.y == 2.0) // Accesses the value of 'y' via its custom name. @assert(tuple.item1 == 2.0) // Accesses the value of 'y' using the default 'item1'. }

Automatic Naming of Tuple Fields

When creating a tuple using variables, Swag automatically assigns the field names based on the variable names, unless explicitly overridden. This feature simplifies tuple creation by directly reflecting variable names within the tuple structure.

#test { let x = 555 let y = 666 let t = {x, y} // Tuple fields are automatically named 'x' and 'y'. @assert(t.x == 555) // Accesses the value of 'x' using the variable name. @assert(t.item0 == t.x) // 'item0' corresponds to the variable 'x'. @assert(t.y == 666) // Accesses the value of 'y' using the variable name. @assert(t.item1 == t.y) // 'item1' corresponds to the variable 'y'. }

Tuple Assignment and Compatibility

Swag provides flexibility in assigning tuples to each other, even if the field names differ. As long as the field types are compatible, tuples can be assigned across different structures, facilitating easy data transfer and manipulation.

#test { var x: { a: s32, b: s32 } // A tuple with fields 'a' and 'b'. var y: { c: s32, d: s32 } // A tuple with fields 'c' and 'd'. y = {1, 2} // Initializes 'y' with integer values. x = y // Assigns 'y' to 'x', field names do not need to match. @assert(x.a == 1) // 'x.a' acquires the value of 'y.c'. @assert(x.b == 2) // 'x.b' acquires the value of 'y.d'. // Although 'x' and 'y' have different field names, their types are not identical. #assert #typeof(x) != #typeof(y) }

Tuple Unpacking

Swag allows tuples to be unpacked, meaning their individual elements can be extracted into separate variables. This feature is particularly useful for disaggregating a tuple's data for specific operations or further processing.

#test { var tuple1 = {x: 1.0, y: 2.0} // A tuple with fields 'x' and 'y'. // Unpacking the tuple: 'value0' gets the value of 'x', and 'value1' gets the value of 'y'. let (value0, value1) = tuple1 @assert(value0 == 1.0) @assert(value1 == 2.0) var tuple2 = {"name", true} // A tuple containing a string and a boolean. let (name, value) = tuple2 // Unpacking into 'name' and 'value'. @assert(name == "name") @assert(value == true) }

Ignoring Tuple Fields During Unpacking

When unpacking a tuple, you can choose to ignore specific fields using the ? placeholder. This is particularly useful when only certain elements of a tuple are needed, allowing you to disregard others efficiently.

#test { var tuple1 = {x: 1.0, y: 2.0} // A tuple with fields 'x' and 'y'. let (x, ?) = tuple1 // Unpacks 'x' and ignores the second field. @assert(x == 1.0) let (?, y) = tuple1 // Ignores the first field and unpacks 'y'. @assert(y == 2.0) }

Enum

Enums in Swag

Enums in Swag allow you to define a set of named values. Unlike C/C++, enum values in Swag can end with ;, ,, or an end of line (eol).

#test { enum Values0 { A B } enum Values1 { A, B, } // The last comma is not necessary enum Values2 { A, B } enum Values3 { A, B } enum Values4 { A; B; } enum Values5 { A; B } }

Enum Underlying Type

By default, an enum in Swag is of type s32, meaning the underlying storage for each value is a 32-bit signed integer.

#test { enum Values { A, B } let type = #typeof(Values) // 'type' here is a 'typeinfo' dedicated to the enum type. // In that case, 'type.rawType' represents the enum's underlying type. @assert(type.rawType == s32) #assert #typeof(Values) == Values }

Custom Enum Underlying Type

You can specify a custom underlying type for an enum by appending it after the enum name.

#test { enum Values1: s64 // Force the underlying type to be 's64' instead of 's32' { A B C } #assert #typeof(Values1).rawType == s64 #assert #typeof(Values1.A) == Values1 }

Default and Custom Enum Values

Enum values, if not explicitly specified, start at 0 and increase by 1 for each subsequent value.

#test { enum Value: s64 { A B C } #assert Value.A == 0 #assert Value.B == 1 #assert Value.C == 2 }

You can specify custom values for each enum element.

#test { enum Value: s64 { A = 10 B = 20 C = 30 } #assert Value.A == 10 #assert Value.B == 20 #assert Value.C == 30 }

Incremental Enum Values

If you omit a value after assigning a specific one, it will be assigned the previous value plus 1.

#test { enum Value: u32 { A = 10 B // Will be 11 C // Will be 12 } #assert Value.A == 10 #assert Value.B == 11 #assert Value.C == 12 }

Non-Integer Enum Values

For non-integer types, you must specify the values explicitly, as they cannot be deduced automatically.

#test { enum Value1: string { A = "string 1" B = "string 2" C = "string 3" } #assert Value1.A == "string 1" #assert Value1.B == "string 2" #assert Value1.C == "string 3" enum Value2: f32 { A = 1.0 B = 3.14 C = 6.0 } #assert Value2.A == 1.0 #assert Value2.B == 3.14 #assert Value2.C == 6.0 }

Counting Enum Values

@countof can be used to return the number of values defined in an enum.

#test { enum Value: string { A = "string 1" B = "string 2" C = "string 3" } @assert(@countof(Value) == 3) #assert @countof(Value) == 3 }

Using using with Enums

You can use the using keyword to make enum values accessible without needing to specify the enum name.

#test { enum Value { A B C } using Value // No need to prefix with 'Value.' @assert(A == 0) @assert(B == 1) @assert(C == 2) // The normal form is still possible @assert(Value.A == 0) @assert(Value.B == 1) @assert(Value.C == 2) }

Enums as Flags

Enums can be used as flags if declared with the #[Swag.EnumFlags] attribute. For this, the enum's underlying type should be u8, u16, u32, or u64. By default, enums declared as flags start at 1 (not 0), and each value is typically a power of 2.

#test { #[Swag.EnumFlags] enum MyFlags: u8 { A // First value is 1 B // Value is 2 C // Value is 4 D // Value is 8 } #assert MyFlags.A == 0b00000001 #assert MyFlags.B == 0b00000010 #assert MyFlags.C == 0b00000100 #assert MyFlags.D == 0b00001000 let value = MyFlags.B | MyFlags.C @assert(value == 0b00000110) @assert(value & MyFlags.B == MyFlags.B) @assert(value & MyFlags.C == MyFlags.C) }

Enums with Arrays

You can define an enum where each value is associated with a const static array.

#test { enum Value: const [2] s32 { A = [1, 2] B = [10, 20] } #assert Value.A[0] == 1 #assert Value.A[1] == 2 #assert Value.B[0] == 10 #assert Value.B[1] == 20 }

Enums with Slices

Similarly, you can define an enum where each value is associated with a const slice.

#test { enum Value: const [..] s32 { A = [1, 2] B = [10, 20, 30, 40] } #assert @countof(Value.A) == 2 #assert @countof(Value.B) == 4 let x = Value.A @assert(x[0] == 1) @assert(x[1] == 2) let y = Value.B @assert(y[0] == 10) @assert(y[1] == 20) }

Nested Enums

Enums can be nested within other enums using the using keyword. Both enums must share the same underlying type.

enum BasicErrors { FailedToLoad FailedToSave } // The enum 'BasicErrors' is nested inside 'MyErrors' enum MyErrors { using BasicErrors NotFound = 100 }

Accessing Nested Enums

To access a value inside a nested enum, use the enum name (a scope is created).

const MyError0 = MyErrors.BasicErrors.FailedToSave

Automatic Cast with Nested Enums

An automatic cast occurs when converting a nested enum to its parent enum. For example, a value of type BasicErrors can be passed to a parameter of type MyErrors because MyErrors includes BasicErrors.

#test { const E0: MyErrors = MyErrors.BasicErrors.FailedToLoad const E1: BasicErrors = BasicErrors.FailedToLoad func toto(err: MyErrors) { @assert(err == BasicErrors.FailedToLoad) @assert(err == MyErrors.BasicErrors.FailedToLoad) } toto(E0) toto(E1) // Automatic cast from 'BasicErrors' to 'MyErrors' }

Specific Attributes for Enums

You can use #[Swag.EnumIndex] to enable an enum value to be used directly as an index without needing to cast it. The underlying enum type must be an integer type.

#test { #[Swag.EnumIndex] enum MyIndex { First, Second, Third } const Array = [0, 1, 2] const Valu = Array[MyIndex.First] // No need to cast 'MyIndex.First' }

Preventing Duplicate Enum Values

You can use #[Swag.NoDuplicate] to prevent duplicated values inside an enum. If the compiler detects a value defined more than once, it will raise an error.

#test { #[Swag.NoDuplicate] enum MyEnum { Val0 = 0 //Val1 = 0 // Will raise an error because '0' is already defined } }

Enum Type Inference

Enums in Swag allow type inference, so you can often omit the enum type when assigning a value.

Type Inference in Assignments

The enum type is not necessary in the assignment expression when declaring a variable, as it can be deduced.

#test { enum Values { A; B; } // The explicit form let x: Values = Values.A // The inferred form let y: Values = A @assert(x == y) }
Type Inference in switch Statements

The enum type is not necessary in a case expression of a switch block, as it is inferred from the switch expression.

#test { enum Values { A; B; } let x = Values.A // The explicit form switch x { case Values.A: break case Values.B: break } // The simplified form switch x { case A: break case B: break } }
Simplified Enum Syntax

In expressions where the enum type can be deduced, you can omit the enum name and use the .Value syntax.

#test { enum Values { A, B } // The explicit form let x = Values.A // The simplified form, since 'Values' can be inferred from 'x' @assert(x == .A) @assert(x != .B) }
Simplified Syntax for Enum Flags

This also works for enums used as flags.

#test { #[Swag.EnumFlags] enum Values { A, B } let x = Values.A | Values.B @assert((x & .A) and (x & .B)) }
Simplified Enum Syntax in Function Calls

In most cases, the simplified .Value syntax also works when passing enum values to functions.

#test { enum Values { A; B; } func toto(v1, v2: Values) {} toto(.A, .B) }

Visiting Enum Values

Using type reflection, Swag allows you to iterate over all the values of an enum. This feature is particularly useful when you need to perform operations across all enum values or when you want to dynamically generate behavior based on the values of an enum.

The two primary mechanisms for iterating over enum values are the for construct and the foreach statement.

Looping Over Enum Values

The for construct allows you to iterate over all values in an enum.

#test { enum RGB { R, G, B } // Define a simple enum with three values: R, G, and B // Counting Enum Values var cpt = 0 for idx in RGB: // Iterate over all enum values using 'for' cpt += 1 // Increment counter for each value @assert(cpt == 3) // Assert that the enum has exactly 3 values // Note that '@countof(RGB)' will give you the exact same result @assert(cpt == @countof(RGB)) }

The for idx in RGB: statement is a powerful construct that allows you to iterate over all values in the RGB enum. During each iteration, idx holds the current enum value, which can be used within the for body. In the example above, the for simply counts the number of values in the enum, asserting that the total is 3.

This method is useful when you need to apply the same operation to each enum value, such as populating a list, checking conditions, or performing calculations.

Visiting Enum Values

The foreach statement offers a more structured way to iterate over an enum and perform specific actions based on the current enum value.

#test { enum RGB { R, G, B } // Define a simple enum with three values: R, G, and B // Performing Actions Based on Enum Values foreach val in RGB // Use 'foreach' to iterate over each value in the enum { switch val // A switch statement to perform actions based on the enum value { case R: // Case for the value 'R' // Perform action for 'R' break case G: // Case for the value 'G' // Perform action for 'G' break case B: // Case for the value 'B' // Perform action for 'B' break default: // This should never be reached as we are covering all enum cases @assert(false) } } }

The foreach val in RGB statement offers a more structured way to iterate over an enum. Each iteration provides the current enum value in the val variable, which can then be used in a switch statement (or other logic) to perform specific actions based on the value.

In the provided example, the switch statement checks the value of val and executes different blocks of code depending on whether val is R, G, or B. This is particularly useful when you need to perform different actions based on the specific value of the enum, rather than treating all values the same.

Impl

Utilizing the impl Keyword with Enums in Swag

In Swag, the impl keyword allows you to define methods and functions within the scope of an enum. This is particularly useful for associating specific behaviors directly with the enum values. The self keyword within these methods refers to the current enum instance, providing a clean and organized way to encapsulate logic related to the enum.

// Define an enum RGB with three possible values enum RGB { R // Represents the Red color G // Represents the Green color B // Represents the Blue color }

Implementing Methods for an Enum

In the following example, we utilize the impl enum syntax to define methods for the RGB enum. This syntax allows us to extend the functionality of the enum by associating methods with it. The methods we define will operate on the enum's values, providing a clear and structured approach to handling logic related to these values.

impl enum RGB { // Method to check if the current enum value is `R`. func isRed(self) => self == R // Method to check if the current enum value is either `R` or `B`. func isRedOrBlue(self) => self == R or self == B } #test { // Testing the `RGB` enum methods // Verify if `RGB.R` is recognized as red @assert(RGB.isRed(RGB.R)) // Verify if `RGB.B` is recognized as either red or blue @assert(RGB.isRedOrBlue(RGB.B)) }

Applying the using Keyword with Enum Methods

The using keyword simplifies method calls by allowing you to omit the enum type when invoking methods. This results in more concise and readable code, especially when the enum type is used frequently within a block.

#test { using RGB // Test the method without specifying the enum type @assert(isRedOrBlue(R)) @assert(isRedOrBlue(B)) }

Introduction to Uniform Function Call Syntax (UFCS)

Uniform Function Call Syntax (UFCS) in Swag enhances code readability by allowing methods to be called directly on enum values. This feature leads to more intuitive and clean code, particularly when working with enums.

#test { using RGB @assert(R.isRedOrBlue()) // Verify that `RGB.G` is not recognized as either red or blue @assert(!RGB.G.isRedOrBlue()) }

Union

Understanding union in Swag

A union is a specialized type of struct where all fields share the same memory location, meaning they all start at the same offset (0). This allows multiple fields to occupy the same space in memory, making it possible to store different types of data in the same memory space at different times. This feature is particularly useful for memory optimization in scenarios where only one of the fields needs to be used at any given moment.

#test { // Define a union with three fields: x, y, and z, all of type f32. union MyUnion { x, y, z: f32 // Each field shares the same memory location } // Initialize the union, setting the value of the 'x' field to 666. var v = MyUnion{x: 666} // 'x' is set, and by extension, 'y' and 'z' as well // Since all fields share the same memory, setting 'x' also sets 'y' and 'z' to the same value. @assert(v.y == 666) // Assert that 'y' equals 666 @assert(v.z == 666) // Assert that 'z' equals 666 }

Explanation of Union Behavior

In this example, the union type allows you to define multiple fields (x, y, z), all of which occupy the same memory location. Because these fields overlap in memory, changing the value of one field automatically changes the others. This behavior is useful in situations where you need to store and access different data types within the same memory space, albeit not simultaneously. Here, x, y, and z are all of type f32, so when x is set to 666, both y and z reflect this change, illustrating the shared memory concept inherent in unions.

Pointers

Single Value Pointers

A pointer to a single element is declared using the * symbol. This allows you to create a pointer that holds the address of one specific instance of a data type. Pointers can be used to reference any type of data, allowing for efficient memory management and manipulation of variables.

#test { var ptr1: *u8 // This is a pointer to a single 'u8' value var ptr2: **u8 // This is a pointer to another pointer, which in turn points to a single 'u8' value }

Null Pointers

In Swag, a pointer can be null, indicating that it does not point to any valid memory location. A null pointer is commonly used to signify that the pointer is uninitialized or that it intentionally points to "nothing". Checking for null pointers before dereferencing is a good practice to avoid runtime errors.

#test { var ptr1: *u8 // Declaring a pointer to 'u8' without initialization @assert(ptr1 == null) // By default, the pointer is null, indicating it doesn't point to any valid memory location }

Taking the Address of a Variable

You can obtain the address of a variable using the & operator. This operation returns a pointer to the variable, which can then be stored and used to access or modify the variable's value indirectly through its memory address.

#test { var arr = 1 // Declare an integer variable let ptr = &arr // Take the address of the variable 'arr' @assert(#typeof(ptr) == *s32) // The type of 'ptr' is a pointer to 's32', the type of 'arr' }

Dereferencing a Pointer

Dereferencing a pointer involves accessing the value stored at the memory location that the pointer refers to. In Swag, you use the dref intrinsic for dereferencing, which allows you to retrieve the actual data stored at the pointer's address.

#test { var arr = 42 // Declare an integer variable with value 42 let ptr = &arr // Take the address of 'arr' @assert(dref ptr == 42) // Dereference the pointer to access the value of 'arr', which should be 42 }

Const Pointers

A const pointer in Swag refers to a pointer that cannot change the memory location it points to. However, the data at that memory location can still be mutable. Const pointers are useful when you want to ensure that a pointer always refers to the same memory address throughout its lifetime.

#test { let str = "string" // Declare a string variable let ptr: const *u8 = @dataof(str) // A const pointer to a 'u8' value @assert(dref ptr == 's') // Dereferencing the pointer to get the first character of the string }

Combining const with Pointers

Swag allows you to combine const with pointers in different ways, depending on the level of immutability you need. You can create a pointer that is constant itself, a pointer to constant data, or both. The choice depends on whether you want to restrict the pointer from changing its target or the data being pointed to, or both.

#test { var ptr: *const *u8 // A pointer to a const pointer to 'u8' var ptr1: const *const *u8 // A const pointer to a const pointer to 'u8' var ptr2: const **u8 // A const pointer to a normal pointer to 'u8' }

Multiple Values Pointers

For cases where you need pointer arithmetic and want a pointer to multiple values (like an array or a memory block), you declare your pointer with ^ instead of *. This type of pointer is especially useful for iterating over a sequence of elements stored in contiguous memory locations.

#[Swag.Safety("sanity", false)] #test { var ptr: ^u8 // Declaring a pointer to a memory block of 'u8' values // Pointer arithmetic allows you to navigate through the memory block by modifying the pointer's address ptr = ptr - 1 // Move the pointer back by one 'u8' }

Pointer Arithmetic and Array Elements

When you take the address of an array element, the resulting pointer is treated as pointing to multiple elements, allowing for pointer arithmetic. This enables operations like incrementing or decrementing the pointer, which is useful for traversing arrays.

#test { var x: [4] s32 // Declare an array of 4 's32' elements var ptr = &x[1] // Take the address of the second element // Pointer arithmetic is now enabled because 'ptr' is treated as pointing to multiple values ptr = ptr - 1 // Move the pointer back to the first element #assert #typeof(ptr) == ^s32 // The type of 'ptr' is now a pointer to multiple 's32' values }

Dereferencing with Indexes

When pointer arithmetic is enabled, you can dereference the pointer using an index, allowing access to elements in an array-like manner. This is particularly useful when working with arrays through pointers, as it allows for flexible element access.

#test { var arr = [1, 2, 3, 4] // Declare and initialize an array of 's32' elements let ptr = &arr[0] // Take the address of the first element @assert(#typeof(ptr) == ^s32) // The pointer type allows pointer arithmetic // Dereferencing by index to access the elements let value1 = ptr[0] // Access the first element @assert(value1 == 1) // Verify the first element's value is 1 #assert #typeof(value1) == s32 // Assert the type of value1 is 's32' let value2 = ptr[1] // Access the second element @assert(value2 == 2) // Verify the second element's value is 2 #assert #typeof(value2) == s32 // Assert the type of value2 is 's32' // Using 'dref' for the first element, equivalent to ptr[0] let value = dref ptr // Dereference the pointer to access the first element @assert(value == 1) // Verify the dereferenced value is 1 #assert #typeof(value) == s32 // Assert the type of the dereferenced value is 's32' }

References

References in Swag

Swag also supports references, which are pointers that behave like values. References in Swag provide a convenient way to work with memory addresses while abstracting away the need for explicit pointer syntax, making them easier and safer to use in many cases.

#test { var x = 42 // Use '&' to declare a reference. // Here, we declare a reference to the variable 'x'. // Unlike C++, you must take the address of 'x' to convert it into a reference. let myRef: const &s32 = &x // Declare a constant reference to 'x' // References behave like values, so no explicit dereferencing is needed. // You can think of this as an alias for 'x'. @assert(myRef == 42) // Verify the reference holds the value of 'x' }

Assigning to References

When an assignment is made to a reference outside of its initialization, the operation changes the value of the variable being referenced, not the reference itself.

#test { var x = 42 var myRef: &s32 = &x // The reference is mutable because it's not declared 'const' @assert(myRef == 42) // Confirm the reference initially points to 'x' (value 42) // This changes the value of 'x' through the reference. myRef = 66 // Update the value through the reference @assert(myRef == 66) // Ensure the reference now holds the updated value // Since 'myRef' is an alias for 'x', 'x' is also updated. @assert(x == 66) // Confirm the original variable 'x' has been updated as well }

Reassigning References

Unlike in C++, you can change the reference itself (reassign it) rather than the value it points to. To reassign the reference, use the ref modifier in the assignment.

#test { var x = 1 var y = 1000 var myRef: &s32 = &x // Initialize reference to point to 'x' @assert(myRef == 1) // Verify the reference points to 'x' (value 1) // Here, we reassign 'myRef' to point to 'y' instead of 'x'. // The value of 'x' remains unchanged. myRef = #ref &y // Reassign reference to point to 'y' @assert(myRef == 1000) // Ensure the reference now points to 'y' (value 1000) }

Passing References to Functions

Most of the time, you need to take the address of a variable to create a reference to it. The only exception is when passing a reference to a function parameter, and the reference is declared as const. In such cases, taking the address explicitly is not necessary.

#test { // We can pass a literal directly because 'x' in the function 'toto' is a const reference. // No need to take the address manually. toto(4) // Pass a literal to the function 'toto' } func toto(x: const &s32) { @assert(x == 4) // Validate the received reference matches the passed literal // Internally, the reference is still an address to an 's32'. let ptr = &x // Obtain the address from the reference @assert(dref ptr == 4) // Dereference the pointer to validate the value }

Using References with Structs

This approach is particularly useful for structs, as it allows passing literals directly to functions.

// Our first simple struct! struct MyStruct { x: s32, y: s32 } #test { titi0({1, 2}) // Test passing a literal tuple to 'titi0' titi1({3, 4}) // Test passing a literal tuple to 'titi1' titi2({5, 6}) // Test passing a struct to 'titi2' } func titi0(param: const &{ x: s32, y: s32 }) { // We'll discuss tuples and field naming later, but for now, we can access tuple items by position. @assert(param.item0 == 1) // Validate first tuple item @assert(param.item1 == 2) // Validate second tuple item }

Equivalent Reference Passing

Note that declaring a tuple type or a struct type as a parameter is equivalent to passing a constant reference.

func titi1(param: { x: s32, y: s32 }) { @assert(param.x == 3) // Access struct fields by name @assert(param.y == 4) // Validate field values } func titi2(param: MyStruct) { @assert(param.x == 5) // Access fields of 'MyStruct' @assert(param.y == 6) // Validate field values }

Any

The any Type in Swag

any is a versatile type in Swag that can reference values of any other type. This allows for dynamic type handling within your code.

Warning

any is not a variant. It's a dynamically typed reference to an existing value. This means that any does not store a copy of the value, but rather a reference to the actual value, along with its type information.

#test { var a: any // Declare a variable of type 'any' // Store an 's32' literal value in the 'any' a = 6 // Access the value stored inside 'any' by casting it to the desired type. @assert(cast(s32) a == 6) // Now store a string instead of the 's32' value a = "string" @assert(cast(string) a == "string") // Now store a bool instead a = true @assert(cast(bool) a == true) }

Working with any and Pointers

any acts as a reference to the value it holds, alongside the typeinfo that describes the value's type. You can use @dataof to obtain the pointer to the actual value.

#test { let a: any = 6 // Store an s32 value in 'a' let ptr = cast(const *s32) @dataof(a) // Retrieve the pointer to the stored value @assert(dref ptr == 6) // Dereference the pointer to get the value }

Type Information and any

When #typeof is used on an any, it returns any as the type because any is the type of the reference. To obtain the underlying type of the value stored in any, use @kindof, which is evaluated at runtime.

#test { var a: any = "string" // Store a string in 'a' #assert #typeof(a) == any // The type of 'a' is 'any' @assert(@kindof(a) == string) // The underlying type of the value is 'string' a = true // Change the stored value to a bool @assert(@kindof(a) == bool) // Now the underlying type is 'bool' }

Retrieving Values from any

You can retrieve the value stored in an any either directly or as a constant reference. This provides flexibility in how you interact with the stored value.

#test { let a: any = 42 // Store an s32 value in 'a' #assert #typeof(a) == any // The type of 'a' is 'any' @assert(@kindof(a) == s32) // The underlying type is 's32' let b = cast(s32) a // Get the value itself @assert(b == 42) let c = cast(const &s32) a // Get a constant reference to the value @assert(c == 42) }

Arrays of any

You can create arrays containing multiple types using any. This allows you to maintain a heterogeneous collection where each element can be of a different type, enabling you to manage and manipulate data of various types within a single structure.

#test { var array: [] any = [true, 2, 3.0, "4"] // An array containing different types @assert(@kindof(array[0]) == bool) // The first element is a bool @assert(@kindof(array[1]) == s32) // The second element is an s32 @assert(@kindof(array[2]) == f32) // The third element is an f32 @assert(@kindof(array[3]) == string) // The fourth element is a string @assert(cast(bool) array[0] == true) @assert(cast(s32) array[1] == 2) @assert(cast(f32) array[2] == 3.0) @assert(cast(string) array[3] == "4") }

Nullability of any

An any value can be set to null and tested against null, similar to pointers or other nullable types. This allows for flexible handling of cases where a value may or may not be set, providing a robust approach to managing optional values.

#test { var x: any // Declare an 'any' type variable @assert(x == null) // Initially, 'x' is null x = 6 // Store an s32 value in 'x' @assert(x != null) // Now 'x' holds a value @assert(cast(s32) x == 6) x = "string" // Change the stored value to a string @assert(x != null) @assert(cast(string) x == "string") x = null // Set 'x' back to null @assert(x == null) }

Type Checking with any

An any value can be tested against a type using == and !=. This effectively uses @kindof to retrieve the underlying type for comparison. This is particularly useful when you need to confirm the type of the value inside any before performing type-specific operations.

#test { var x: any // Declare an 'any' type variable @assert(x == null) // 'x' is null @assert(x != s32) // 'x' does not contain an s32 x = 6 // Store an s32 value in 'x' @assert(x == s32) // Now 'x' contains an s32 @assert(x != bool) // But it does not contain a bool x = "string" // Change the stored value to a string @assert(x != s32) // Now 'x' contains a string, not an s32 @assert(x == string) // Confirm it holds a string struct A {} // Define a simple struct x = A{} // Store an instance of A in 'x' @assert(x == A) // 'x' is of type A }

Control flow

If

Basic Usage of if

A basic test with an if statement.

In Swag, curly braces {} are optional for control structures like if. However, if you choose to omit them, you must use a colon :. This syntax rule also applies to other control structures such as while and for.

Unlike in C/C++, the condition in an if statement does not need to be enclosed in parentheses. Parentheses can be used for clarity or grouping, but they are not mandatory.

#test { var a = 0 if a == 1: // No curly braces, so a colon ':' is required after the condition @assert(false) // This block will not execute since 'a' is not equal to 1 if (a == 1): // Parentheses can be used for grouping or clarity, but are optional @assert(false) // This block will also not execute if a == 0 // Curly braces are used, no colon is needed here { @assert(true) // This block will execute because 'a' equals 0 } // The 'else' keyword can be used as in most programming languages. // When not using curly braces, the colon ':' after the condition is mandatory. if a == 0: a += 1 // This block will execute, setting 'a' to 1 else: a += 2 // This block is skipped because the 'if' condition is true @assert(a == 1) // Asserts that 'a' is now 1 // The 'elif' keyword is used for else-if chains, functioning similarly to 'else if' in other languages. if a == 1: a += 1 // This block will execute, making 'a' equal to 2 else: if a == 2: // This nested 'if' will not execute @assert(false) elif a == 3: // This condition is also false @assert(false) elif a == 4: // This condition is false as well @assert(false) // Logical expressions using 'and' and 'or' work as expected if a == 0 and a == 1: // This condition is false because 'a' cannot be both 0 and 1 @assert(false) // This block will not execute if a == 0 or a == 1: // This condition is false because 'a' is 2 @assert(false) if a == 1 or a == 2: // This condition is true because 'a' is 2 @assert(true) // This block will execute }

Variable Declaration in if

You can declare and test a variable in an if statement simultaneously. In this context, the use of var, let, or const is mandatory.

The condition in the if statement will automatically convert the declared variable to a boolean. If the variable is non-zero, the condition evaluates to true, and the if block will execute.

#test { // Declare and test 'a' within the 'if' statement. Since 'a' is 0, the 'if' block will not execute. // The variable 'a' is only scoped within this 'if' block. if let a = 0 { @assert(false) // This block will not execute } // You can redeclare 'a' as a constant in another block. // Since 'a' is 1, the 'if' block will execute. if const a = 1: @assert(a == 1) // This block will execute and assert that 'a' is 1 else: @assert(false) // This block will not execute // Another example using 'let' if let a = 1: @assert(a == 1) // This block will execute, confirming that 'a' is 1 else: @assert(false) // This block will not execute }

Adding Conditions with where

When an if statement includes a variable declaration, you can add an additional condition using a where clause. The where clause is evaluated only if the initial test passes.

#test { func retSomething()->string => "string" func retNothing()->string => null // The 'where' clause is only evaluated if 'str' is not null. if let str = retSomething() where str[0] == 's': @assert(true) // This block will execute since 'str' starts with 's' else: @assert(false) // This block will not execute // In this example, the initial test fails because 'str' is null, // so the 'where' clause is not evaluated. if let str = retNothing() where str[0] == 's': @assert(false) // This block will not execute else: @assert(true) // This block will execute since the initial test fails }

Loop

Introduction to for

The for construct in Swag is a tool for iteration, allowing developers to repeat a block of code a specified number of times. This guide provides an in-depth exploration of the for construct, covering its various features, including basic usage, indexing, naming, reverse loops, early exits, and advanced filtering with the where clause.

Basic Usage

The for expression dictates the number of iterations and is evaluated only once before the loop begins. This value must be a positive integer.

#test { var cpt = 0 for 10: // Executes the loop 10 times cpt += 1 // Increment the counter on each iteration @assert(cpt == 10) // Assert that the loop executed exactly 10 times }

Using #index

Within a for, the compiler automatically provides the #index keyword, which holds the current iteration index, starting from 0.

#test { var cpt = 0'u64 for 5 // Loop 5 times { cpt += #index // Add the current index value to 'cpt' in each iteration } @assert(cpt == 0 + 1 + 2 + 3 + 4) // Assert that 'cpt' equals the sum of the indices }

Naming the Loop Index

Assigning a name to the for index can improve code readability and clarify the for's intent.

#test { var cpt = 0 var cpt1 = 0 for i in 5 // The `for` index is named 'i' { cpt += i // Use the named index 'i' cpt1 += #index // '#index' is still accessible and returns the same value as 'i' } @assert(cpt == 0 + 1 + 2 + 3 + 4) // Confirm that 'cpt' equals the sum of indices @assert(cpt1 == cpt) }

Looping Over Arrays and Slices

The for construct is versatile and can iterate over any collection type that supports the @countof intrinsic, such as arrays, slices, and strings.

#test { var arr = [10, 20, 30, 40] // Define an array with 4 elements #assert @countof(arr) == 4 // Verify the array has 4 elements var cpt = 0 for arr: // Loop over the array's elements cpt += arr[#index] // Add the current element's value to 'cpt' @assert(cpt == 10 + 20 + 30 + 40) // Verify that 'cpt' equals the sum of the array elements }
Warning

When looping over a string, the for will iterate over each byte, not over runes. For handling runes (characters that may be encoded in multiple bytes), consider using the Std.Core module.

#test { var cpt = 0 for "⻘": // Loop over each byte in the string "⻘" cpt += 1 // Increment the counter for each byte @assert(cpt == 3) // Assert that the character '⻘' consists of 3 bytes }

Reverse Looping

To iterate in reverse order, append the #back modifier to the for statement.

#test { var cpt = 0 // Loop in reverse order, starting from index 2 down to 0 for #back 3 { if cpt == 0: @assert(#index == 2) // First iteration, index should be 2 elif cpt == 1: @assert(#index == 1) // Second iteration, index should be 1 elif cpt == 2: @assert(#index == 0) // Third iteration, index should be 0 cpt += 1 } }

break and continue

The break and continue keywords provide control over the for's execution flow. break exits the loop early, while continue skips the remainder of the current iteration and proceeds with the next.

Exiting Early with break

The break keyword allows you to exit the loop before completing all iterations, useful for optimizing performance or when a specific condition is met.

#test { var cpt = 0 for x in 10 // Loop 10 times with index named 'x' { if x == 5: break // Exit the loop when 'x' equals 5 cpt += 1 } @assert(cpt == 5) // Confirm that the loop executed 5 times }

Skipping Iterations with continue

The continue keyword skips the rest of the current for iteration and jumps to the next, which is useful when certain conditions should bypass processing.

#test { var cpt = 0 for x in 10 // Loop 10 times with index named 'x' { if x == 5: continue // Skip the iteration when 'x' equals 5 cpt += 1 } @assert(cpt == 9) // Confirm that the loop executed 9 times, skipping the 5th iteration }

Ranges

The for construct supports iteration over a range of signed values, enabling flexible iteration over specified intervals.

Looping Over a Range with to

The to keyword defines a loop that iterates from one value to another, inclusive.

Warning

The start value must be less than or equal to the end value.

#test { var count = 0 var sum = 0 for i in -1 to 1 // Loop from -1 to 1, inclusive { count += 1 // Count the number of iterations sum += i // Sum the current index value } @assert(sum == 0) // Verify that the sum is 0 (-1 + 0 + 1) @assert(count == 3) // Confirm that the loop executed 3 times }

Excluding the Last Value with until

The until keyword enables iteration up to, but not including, the end value.

#test { var cpt = 0 for i in 1 until 3 // Loop from 1 up to, but excluding, 3 { cpt += i // Add the current index value to 'cpt' } @assert(cpt == 1 + 2) // Verify that 'cpt' equals the sum of 1 and 2 }

Reverse Range Looping

When using ranges, you can also iterate in reverse order by adding the #back modifier after the for statement.

#test { for #back 0 to 5 // Loop from 5 down to 0, inclusive { } for #back -1 to 1 // Loop from 1 down to -1, inclusive { } for #back -2 until 2 // Loop from 1 down to -2, excluding 2 { } }

Infinite Loop

A for without an expression but with a block of code creates an infinite loop, functionally equivalent to while true {}. Infinite loops are often controlled with break statements.

#test { for { if #index == 4: // Use `#index` to break the for after 4 iterations break } }

Using where Clause

The where clause provides conditional filtering within a loop, allowing specific iterations to execute based on defined criteria.

Basic where Clause

The where clause is appended directly after the for statement, applying a condition to the loop's index or value. Only iterations that satisfy this condition are executed.

#test { var result = 0 // Loop over the range from 0 to 9, but only process even indices. for i in 10 where i % 2 == 0 { result += i // Sum only even indices } @assert(result == 0 + 2 + 4 + 6 + 8) // Verify that 'result' equals the sum of even indices }

where with Arrays

When looping over arrays, the where clause can filter elements based on their value or index, enabling targeted iteration.

#test { var arr = [10, 21, 30, 41, 50] var sumOfEvens = 0 // Loop over the array, summing only the even numbers. for i in arr where arr[i] % 2 == 0 { sumOfEvens += arr[i] // Add the even element values to 'sumOfEvens' } @assert(sumOfEvens == 10 + 30 + 50) // Verify that 'sumOfEvens' equals the sum of even numbers }

Complex Conditions with where

The where supports combining multiple logical expressions, allowing for complex filtering conditions directly within the for.

#test { var arr = [10, 15, 20, 25, 30, 35] var filteredSum = 0 // Loop over the array, summing only even numbers greater than 15. for i in arr where arr[i] % 2 == 0 and arr[i] > 15 { filteredSum += arr[i] // Add the element values that meet the condition } @assert(filteredSum == 20 + 30) // Verify that 'filteredSum' equals the sum of 20 and 30 } #test { var arr = [10, 25, 30, 45, 50, 65] var complexSum = 0 // Loop over the array, summing elements that are either even or greater than 40. for i in arr where arr[i] % 2 == 0 or arr[i] > 40 { complexSum += arr[i] // Add the values that meet the complex condition to 'complexSum' } @assert(complexSum == 10 + 30 + 45 + 50 + 65) // Verify that 'complexSum' equals the sum of matching values }

where with Ranges

The where clause can also be applied to loops over ranges, providing precise control over which range values are processed in the for.

#test { var sumOfPositiveEvens = 0 // Loop over the range from -5 to 5, but process only positive even numbers. for i in -5 to 5 where i > 0 and i % 2 == 0 { sumOfPositiveEvens += i // Add the positive even values to 'sumOfPositiveEvens' } @assert(sumOfPositiveEvens == 2 + 4) // Verify that 'sumOfPositiveEvens' equals the sum of 2 and 4 }

Combining back and where

You can combine the #back modifier with the where clause to filter values while iterating in reverse order.

#test { var arr = [10, 20, 30, 40, 50] var reversedSum = 0 // Loop through the array in reverse order, summing only even values. for #back i in arr where arr[i] % 2 == 0 { reversedSum += arr[i] // Add the even values to 'reversedSum' } @assert(reversedSum == 50 + 40 + 30 + 20 + 10) // Verify the sum of even values in reverse }

For

Introduction to for Loops

The for loop also offers a versatile way to iterate over a range of values. The structure closely follows that of C/C++ loops, consisting of a variable declaration statement, a test expression, and an ending statement. This provides fine-grained control over the for's execution, making it a powerful tool for various iteration scenarios.

#test { var cpt = 0 // A standard 'for' loop with initialization, condition, and increment. for var i = 0; i < 10; i += 1: cpt += 1 @assert(cpt == 10) // Alternative syntax: Semicolons ';' can be replaced by newlines for cleaner, more readable code. for var i = 0 i < 10 i += 1 { cpt += 1 } @assert(cpt == 20) }

Accessing Loop Index with #index

Similar to other looping constructs like for, foreach, and while, the for loop in Swag provides access to the #index keyword. This keyword represents the current for index and is particularly useful when you need to keep track of the iteration count separately from the for variable.

#test { var cpt = 0'u64 // Using `#index` to accumulate the for indices. for var i: u32 = 10; i < 15; i += 1: cpt += #index @assert(cpt == 0+1+2+3+4) var cpt1 = 0'u64 for var i = 10; i < 15; i += 1: cpt1 += #index @assert(cpt1 == 0+1+2+3+4) }

Using break and continue in for Loops

In Swag, break and continue work within for loops just as they do in other for structures. Use break to exit the loop prematurely, effectively terminating the loop when a specific condition is met. The continue statement, on the other hand, skips the remainder of the current for iteration and jumps to the next iteration.

#test { var sum = 0 for var i = 0; i < 10; i += 1 { if i == 5: break // Exit the for when 'i' equals 5 sum += i } @assert(sum == 0+1+2+3+4) // Sum is 10 sum = 0 for var i = 0; i < 10; i += 1 { if i % 2 == 0: continue // Skip even numbers sum += i } @assert(sum == 1+3+5+7+9) // Sum is 25 }

Nested for Loops

Swag supports nested for loops, which are useful for more complex iteration patterns. In nested loops, the #index keyword refers to the current index of the innermost for.

#test { var result = 0'u64 // Outer for for var i = 0; i < 5; i += 1 { // Inner for for var j = 0; j < 5; j += 1 { result += #index // Adds the index of the inner for } } @assert(result == 10 * 5) // Each inner for runs 5 times, so the sum of indices (0+1+2+3+4) * 5 = 10*5 }

Iterating Over Arrays with for

The for for can also be used to iterate over elements of an array or other iterable collections. This method provides a straightforward way to process or manipulate each element within a collection (but we'll see later that foreach is better).

#test { var array = [1, 2, 3, 4, 5] var sum = 0 for var i = 0; i < @countof(array); i += 1 { sum += array[i] } @assert(sum == 1+2+3+4+5) // Sum is 15 }

While

Introduction to while Loops

A while loop is a control flow statement that allows repeated execution of a block of code as long as the specified condition evaluates to true. Once the condition becomes false, the loop terminates.

#test { var i = 0 while i < 10: // Loop executes repeatedly until 'i' is no longer less than 10 i += 1 // Increment 'i' by 1 on each iteration @assert(i == 10) // Verify that 'i' equals 10 after loop completion }

Breaking Out of a while Loop

The break statement provides a way to exit a while loop before the loop’s condition becomes false. This is particularly useful when an early termination of the loop is needed based on a specific condition.

#test { var i = 0 while i < 10 { if i == 5: break // Exit the loop immediately when 'i' reaches 5 i += 1 // Increment 'i' by 1 on each iteration } @assert(i == 5) // Confirm that 'i' equals 5 after breaking out of the loop }

Skipping Iterations with continue

The continue statement allows you to skip the current iteration and proceed directly to the next iteration of the loop. This is useful for ignoring specific conditions within the loop while continuing its execution.

#test { var sum = 0 var i = 0 while i < 10 { i += 1 // Increment 'i' by 1 at the start of each iteration if i % 2 == 0: continue // Skip adding 'i' to 'sum' if 'i' is an even number sum += i // Add only odd numbers to 'sum' } @assert(sum == 25) // Ensure that the sum of odd numbers from 1 to 9 equals 25 }

Nested while Loops

A while loop can contain another while loop, forming a nested loop structure. In such cases, the break and continue statements apply only to the loop in which they are directly placed.

#test { var i = 0 var j = 0 var count = 0 while i < 3 { j = 0 while j < 3 { if j == 2: break // Exit the inner loop when 'j' equals 2 count += 1 // Increment 'count' each time the inner loop completes an iteration j += 1 // Increment 'j' by 1 on each iteration of the inner loop } i += 1 // Increment 'i' by 1 on each iteration of the outer loop } @assert(count == 6) // Confirm that 'count' equals 6, indicating the inner loop ran 6 times }

Using while with Complex Conditions

The condition in a while loop can involve complex logical expressions, allowing for more sophisticated and controlled execution of the loop.

#test { var a = 0 var b = 1 var iterations = 0 while a < 100 and b < 200 { a += 10 // Increment 'a' by 10 on each iteration b += 20 // Increment 'b' by 20 on each iteration iterations += 1 // Increment 'iterations' to track the number of loop executions } @assert(a == 100) // Ensure that 'a' reaches 100 upon loop completion @assert(b == 201) // Ensure that 'b' reaches 201 upon loop completion @assert(iterations == 10) // Confirm that the loop executed 10 times }

Switch

Introduction to switch in Swag

The switch statement in Swag operates similarly to those in C/C++, with a key distinction: Swag does not require an explicit break statement at the end of each case block. Instead, it prevents unintentional fallthrough behavior by design, except when the case is empty, where a break statement is necessary. This ensures that each case is independent unless explicitly designed otherwise, reducing the risk of errors in control flow.

#test { let value = 6 // The `switch` statement evaluates the value of 'value'. // The corresponding `case` block is executed based on the match. switch value { case 0: @assert(false) // Assertion fails if 'value' is 0. case 5: @assert(false) // Assertion fails if 'value' is 5. case 6: @assert(true) // Assertion passes if 'value' is 6. default: @assert(false) // Assertion fails if no cases match. } let ch = 'A''rune // This `switch` checks the value of 'ch'. switch ch { case 'B': @assert(false) // Assertion fails if 'ch' is 'B'. case 'A': break // Exits the switch when 'ch' is 'A'. } }

Multiple Values in a case

Swag allows you to assign multiple values to a single case statement, simplifying the code when the same block of code should execute for several potential matches. This feature enhances the readability and maintainability of your switch statements.

#test { let value = 6 // In this switch, the `case` statement handles multiple values. switch value { case 2, 4, 6: @assert(true) // Assertion passes if 'value' is 2, 4, or 6. default: @assert(false) // Assertion fails if 'value' does not match any case. } // Listing each value on its own line for enhanced readability. switch value { case 2, 4, 6: @assert(true) // Assertion passes for any listed value. default: @assert(false) // Assertion fails if no cases match. } }

Using switch with Various Types

The switch statement in Swag is versatile, supporting any type that implements the == operator. This flexibility extends beyond numeric types to include strings and other comparable types, making it a powerful tool for various decision-making scenarios.

#test { let value = "myString" // A `switch` statement can match string values as well. switch value { case "myString": @assert(true) // Assertion passes if 'value' matches "myString". case "otherString": @assert(false) // Assertion fails if 'value' matches "otherString". default: @assert(false) // Assertion fails if no cases match. } }

Intentional Fallthrough with fallthrough

Swag allows for intentional fallthrough behavior, similar to C/C++, using the fallthrough keyword. This feature provides the option to continue execution from one case block to the next, which can be useful in scenarios where multiple cases share common logic.

#test { let value = 6 // Demonstrating fallthrough where execution continues from `case 6` to `case 7`. switch value { case 6: fallthrough // Explicitly continues to the next `case`. case 7: @assert(value == 6) // Assertion passes, confirming 'value' is 6. default: @assert(true) // Default case is a safeguard; true assertion keeps the test passing. } }

Exiting a case Early with break

The break statement in Swag allows for early exit from a case block. This is particularly useful when only a portion of the block should be executed under specific conditions, enhancing control flow within your switch statements.

#test { let value = 6 // Demonstrating the use of `break` to exit the switch early. switch value { case 6: if value == 6: break // Exits the switch if the condition is true. @assert(false) // This line is never reached if `value` is 6. default: @assert(false) // Assertion fails if no cases match. } }

Handling Empty Cases with break

In Swag, a case statement cannot be left empty. If no action is required for a particular case, the break statement must be used explicitly to avoid compilation errors, ensuring clarity in control flow.

#test { let value = 6 // Handling cases where no action is needed using `break`. switch value { case 5: @assert(false) // Assertion fails if `value` is 5. case 6: break // No action is taken; switch exits cleanly. default: @assert(false) // Assertion fails if no cases match. } }

Variable and Expression Cases

Swag’s switch statement offers the flexibility to use variables and expressions in case conditions. This allows for dynamic evaluation based on runtime values, further extending the utility of the switch statement.

#test { let test = 2 let a = 0 let b = 1 // Demonstrating the use of variables and expressions in switch cases. switch test { case a: // `a` is a variable here, not a constant. @assert(false) // Assertion fails if `test` equals `a` (0). case b: // `b` is another variable. @assert(false) // Assertion fails if `test` equals `b` (1). case b + 1: // This case uses an expression. @assert(true) // Assertion passes if `test` equals `b + 1` (2). } }

The Swag.Complete Attribute

The Swag.Complete attribute ensures exhaustive handling of all possible cases in an enum within a switch statement. If any enum value is not explicitly handled, the compiler will raise an error, enforcing robust and complete logic coverage.

#test { enum Color { Red, Green, Blue } let color = Color.Red #[Swag.Complete] switch color { case Color.Red: break // Handles the `Red` case. case Color.Green: @assert(false) // Assertion fails if `color` is Green. case Color.Blue: @assert(false) // Assertion fails if `color` is Blue. } }

Matching Ranges in a switch Statement

Swag supports matching a range of values in a switch statement, allowing you to group and handle multiple values that fall within a specific range efficiently. This is useful for concise and clear range-based logic within your code.

#test { var success = false let x = 6 // Switch statement using ranges to match a group of values. switch x { case 0 to 5: @assert(false) // Assertion fails if `x` is between 0 and 5. case 6 to 15: success = true // Sets `success` to true if `x` is between 6 and 15. } @assert(success) // Ensures the correct case was matched. }

Overlapping Ranges

In Swag, if ranges within a switch statement overlap, the first valid range that matches will be executed, while subsequent overlapping ranges are ignored. This order of precedence must be carefully considered in your logic.

#test { var success = false let x = 6 // Demonstrating overlapping ranges and their precedence. switch x { case 0 to 10: success = true // This case matches first; subsequent cases are ignored. case 5 until 15: @assert(false) // This is not reached because the first range matches. } @assert(success) // Confirms that the first range was applied correctly. }

Using the where Clause in switch

The where clause in Swag allows you to add additional conditions to a case in a switch statement. This provides finer control over the logic, enabling complex decision-making scenarios where multiple variables influence the outcome.

#test { let x = 6 let y = 10 // Each `case` checks the value of 'x' and applies an additional condition with 'where'. switch x { case 6 where y == 9: @assert(false) // Skipped because `y` is not 9. case 6 where y == 10: @assert(true) // Executes because both `x` and `y` match the conditions. default: @assert(false) // Fallback case; should not be executed here. } }

Using where with default

The where clause can also be applied to a default case, providing conditional logic even when no specific case matches. This allows you to handle default scenarios with more precision, based on additional criteria.

#test { let x = 7 let y = 10 // Demonstrates different outcomes based on 'where' clauses in default. switch x { case 6 where y == 10: @assert(false) // Skipped because `x` is not 6. case 7 where y == 9: @assert(false) // Skipped because `y` is not 9. default where y == 10: break // Executes because `y` is 10 and no previous case matched. default: @assert(false) // General fallback case; should not be executed. } }

Switching on Type with any or interface

When using a switch statement with a variable of type any or interface, Swag matches cases based on the underlying type of the variable. This behavior is akin to the @kindof intrinsic, which allows for dynamic type checking within a switch construct.

#test { let x: any = "value" // Switch statement based on the underlying type of 'x'. switch x // Implicitly checks the type of `x` using `@kindof`. { case string: break // Executes if `x` is of type string. default: @assert(false) // Assertion fails if `x` is not a string. } }

Switch Statement with Type Guard and Variable Binding

You can declare a variable with the var keyword before the case value to bind the matched value to a variable, which can then be used within the corresponding case block. Additionally, you can apply conditional checks directly within the case using the where clause to further refine the matching logic.

The switch statement implicitly checks the type of the variable x using a type guard (@kindof), allowing for more precise case matching based on the variable's runtime type.

Key Features:

  1. Type Guarding: The switch statement evaluates the type of x, allowing cases to be matched based on its type.
  2. Variable Binding: The matched value can be bound to a variable using the var keyword, making the value accessible within the case block.
  3. Conditional Matching: The where clause enables additional conditions to be checked during case matching.

Example 1:

The first example checks if x is a string. If it is, the value is bound to the variable str. The @assert function is then used to ensure that the value of str is equal to "value".

#test { let x: any = "value" // Switch statement based on the underlying type of 'x'. switch x // Implicitly checks the type of `x` using `@kindof`. { case let str as string: // 'str' will contain the value if 'x' is a string @assert(str == "value") break default: @assert(false) } }

Example 2:

The second example showcases conditional matching using the where clause. The switch statement evaluates whether x is a string and checks additional conditions specified in each case.

#test { let x: any = "value" switch x // Implicitly checks the type of `x` using `@kindof`. { case let str as string where str == "value": // Matches if `x` is a string and equals "value" @assert(str == "value") // Asserts that `str` is equal to "value" break case let str as string where str == "not_a_value": // Matches if `x` is a string and equals "not_a_value" @assert(str == "not_a_value") // Asserts that `str` is equal to "not_a_value" break default: @assert(false) // Asserts false if no case matches } }

Switch Without an Expression

A switch statement in Swag can operate without an expression, behaving like a series of if/else statements. Each case is evaluated sequentially, and the first one that evaluates to true is executed, allowing for more complex conditional logic.

#test { let value = 6 let value1 = "true" // Switch statement without an expression, behaving like an if-else chain. switch { case value == 6 or value > 10: @assert(true) // Passes if `value` is 6 or greater than 10. fallthrough // Continues to the next case regardless. case value1 == "true": @assert(true) // Passes if `value1` is "true". default: @assert(false) // Fails if no cases match. } }

Break

Introduction to break in Swag

The break statement is a control structure in Swag, allowing you to exit from for, foreach, while, for, and switch constructs. Understanding how and when to use break is essential for effective flow control in your programs.

#test { for 10: break // Immediately exits the loop, no further iterations occur for var i = 0; i < 10; i += 1: break // Exits the loop instantly after the first iteration while false: break // Demonstrates usage, but this line is never reached due to the false condition }

Default Behavior of break

By default, the break statement exits only the innermost loop or control structure in which it resides. This ensures that nested loops or structures can be controlled independently.

#test { var cpt = 0 for 10 { for 10 { break // Exits the inner loop only, allowing the outer loop to continue } // The outer loop continues its execution here cpt += 1 } @assert(cpt == 10) // Verifies that the outer loop has run exactly 10 times }

Named Scopes with break

Swag allows you to define named scopes using the #scope keyword. The break statement can then be directed to exit from the named scope, offering a structured approach to control flow within complex nested loops or conditions.

#test { var cpt = 0 // Defining a scope named 'BigScope' #scope BigScope { for 10 { cpt += 1 break to BigScope // Exits the entire 'BigScope' scope after incrementing `cpt` } @assert(false) // This assertion is never reached due to the break to statement above } // Execution resumes after 'BigScope' @assert(cpt == 1) // Confirms that the loop inside 'BigScope' executed only once }

Using continue with Named Scopes

The continue statement can be used with a named scope to restart the execution from the beginning of the scope. This can be useful for iterating until a specific condition is met.

#test { var cpt = 0 #scope Loop { cpt += 1 if cpt == 5: break // Exits the 'Loop' scope when `cpt` equals 5, ending the loop continue // Restarts the 'Loop' scope, incrementing `cpt` each time } @assert(cpt == 5) // Verifies that the loop runs exactly 5 times before exiting }

Unnamed Scopes for Flow Control

Named scopes are optional. You can also use unnamed scopes to manage control flow, offering a structured alternative to traditional if/else statements. This can help simplify complex conditions.

#test { let cpt = 0 #scope { if cpt == 1 { @assert(cpt == 1) break // Exits the unnamed scope if `cpt` equals 1 } if cpt == 2 { @assert(cpt == 2) break // Exits the unnamed scope if `cpt` equals 2 } if cpt == 3 { @assert(cpt == 3) break // Exits the unnamed scope if `cpt` equals 3 } } }

Using break with Simple Statements

A scope in Swag can also be followed by a simple statement rather than a block, providing additional flexibility in controlling the flow of execution.

#test { #scope Up for 10 { for 10 { if #index == 5: break to Up // Exits to the 'Up' scope when the inner loop index equals 5 } @assert(false) // This assertion is never reached due to the break to statement above } }

Visit

Introduction to foreach

The foreach statement is designed to iterate over all elements within a collection. It provides a streamlined and efficient way to process each item in a collection, which can be an array, slice, string and even a struct.

#test { // Iterating through each byte in the string "ABC". // The current byte will be stored in the variable 'value'. foreach value in "ABC" { // '#index' is available to store the loop index. let a = #index // Index of the current iteration switch a { case 0: @assert(value == 'A') // Check if the first value is 'A' case 1: @assert(value == 'B') // Check if the second value is 'B' case 2: @assert(value == 'C') // Check if the third value is 'C' } } }

Naming the Value and Index

Both the value and the loop index can be named explicitly. This enhances code readability, especially in cases involving nested loops or complex data structures.

#test { foreach value, index in "ABC" { let a = index // The current loop index switch a { case 0: @assert(value == 'A') // Check if the first value is 'A' case 1: @assert(value == 'B') // Check if the second value is 'B' case 2: @assert(value == 'C') // Check if the third value is 'C' } } }

Using Default Aliases

Both the value and the index are optional. If names are not explicitly provided, the default aliases #alias0 for the value and #alias1 for the index can be used.

#test { foreach "ABC" { let a = #alias1 // Default alias for the index @assert(a == #index) // Ensure alias matches the index switch a { case 0: @assert(#alias0 == 'A') // Check if the first alias is 'A' case 1: @assert(#alias0 == 'B') // Check if the second alias is 'B' case 2: @assert(#alias0 == 'C') // Check if the third alias is 'C' } } }

Reverse Order with #back

To iterate over elements in reverse order, use the #back modifier. This is particularly useful when processing a collection from the last element to the first.

#test { // Visiting each byte in the string "ABC" in reverse order. var cpt = 0 foreach #back value in "ABC" { // '#index' still stores the loop index, even in reverse order. switch cpt { case 0: @assert(value == 'C') // First value should be 'C' @assert(#index == 2) // Index should be 2 case 1: @assert(value == 'B') // Second value should be 'B' @assert(#index == 1) // Index should be 1 case 2: @assert(value == 'A') // Third value should be 'A' @assert(#index == 0) // Index should be 0 } cpt += 1 // Increment the counter } }

Visiting Arrays and Slices

The foreach statement can be used to iterate over arrays or slices, enabling straightforward processing of each element.

#test { var array = [10, 20, 30] // Define an array of integers var result = 0 foreach it in array: // Iterate over each element in the array result += it // Accumulate the values @assert(result == 10 + 20 + 30) // Ensure the sum matches the expected result }

Multi-dimensional Arrays

foreach supports multi-dimensional arrays, facilitating the processing of complex data structures.

#test { var array: [2, 2] s32 = [[10, 20], [30, 40]] // Define a 2x2 array var result = 0 foreach it in array: // Iterate over each element in the 2D array result += it // Accumulate the values @assert(result == 10 + 20 + 30 + 40) // Ensure the sum matches the expected result }

Modifying Elements with &

By prefixing the value name with &, foreach allows you to visit elements by address, enabling in-place modification of the elements.

#test { var array: [2, 2] s32 = [[1, 2], [3, 4]] // Define a 2x2 array var result = 0 foreach &it in array { result += dref it // Accumulate the values dref it = 555 // Modify each element in place } @assert(result == 1 + 2 + 3 + 4) // Ensure the sum matches the expected result @assert(array[0, 0] == 555) // Verify the first element is modified @assert(array[0, 1] == 555) // Verify the second element is modified @assert(array[1, 0] == 555) // Verify the third element is modified @assert(array[1, 1] == 555) // Verify the fourth element is modified }

Filtering with where

The where clause can be used with foreach to filter the elements processed based on specific conditions. This approach is efficient for conditionally applying logic to only the elements that meet certain criteria.

#test { var array: [] s32 = [1, 2, 3, 4] // Define an array of integers var result = 0 // Process only even values using `where`. foreach value in array where value & 1 == 0: result += value // Accumulate only even values @assert(result == 6) // Ensure the sum of even values is correct // Equivalent using an if statement inside the `foreach` loop: result = 0 foreach value in array: if value & 1 == 0: // Check if the value is even result += value // Accumulate even values @assert(result == 6) // Ensure the sum of even values is correct // Equivalent using `continue` to skip odd values: result = 0 foreach value in array { if (value & 1) != 0: // Skip odd values continue result += value // Accumulate even values } @assert(result == 6) // Ensure the sum of even values is correct }

Structs

Declaration

Basic Struct Declaration

This section illustrates a basic struct declaration in Swag. Notice that the var keyword is not required when declaring fields within the struct. Each field is defined with a specific type.

#test { struct MyStruct { name: string // Field 'name' of type 'string' } struct MyStruct1 { x: s32 // Field 'x' of type 's32' y, z: s32 // Fields 'y' and 'z' of type 's32', declared together val: bool // Field 'val' of type 'bool' myS: MyStruct // Field 'myS' of type 'MyStruct', demonstrating a nested struct } }

Field Separators

Fields within a struct can be separated by either a comma , or a semicolon ;. The trailing separator is optional and can be omitted.

#test { struct MyStruct { name: string, val1: bool } // Fields separated by commas struct MyStruct1 { x: s32; y, z: s32; val: bool; myS: MyStruct } // Fields separated by semicolons }

Anonymous Structs

Structs can be declared anonymously when used as variable types. This is particularly useful for lightweight, temporary groupings of data.

#test { var tuple: struct { x: f32 // Field 'x' of type 'f32' y: f32 // Field 'y' of type 'f32' } var tuple1: struct{ x, y: f32 } // Anonymous struct with fields 'x' and 'y' of type 'f32' tuple.x = 1.0 // Assigning value 1.0 to 'x' tuple.y = 2.0 // Assigning value 2.0 to 'y' @assert(tuple.x == 1.0) // Asserting 'x' is 1.0 @assert(tuple.y == 2.0) // Asserting 'y' is 2.0 } #test { struct MyStruct { rgb: struct{ x, y, z: f32 } // Nested anonymous struct for RGB values hsl: struct{ h, s, l: f32 } // Nested anonymous struct for HSL values } }

Default Field Values

Fields within a struct can be initialized with default values, providing a convenient way to ensure fields are set to a known state.

#test { struct MyStruct { x: s32 = 666 // Field 'x' initialized with default value 666 y: string = "454" // Field 'y' initialized with default value "454" } var v = MyStruct{} // Initializing struct with default values @assert(v.x == 666) // Asserting 'x' has the default value 666 @assert(v.y == "454") // Asserting 'y' has the default value "454" }

Struct Initialization

Struct variables can be initialized in multiple ways, providing flexibility in how you set up your structs.

#test { // Initializing fields directly within the struct declaration struct MyStruct { x, y: s32 = 1 // Both 'x' and 'y' initialized to 1 } // Without parameters, all fields take their default values as defined in the struct var v0: MyStruct // Struct with default initialization @assert(v0.x == 1) // Asserting 'x' is 1 @assert(v0.y == 1) // Asserting 'y' is 1 // Initializing with parameters within {..}, in the order of field declarations var v1: MyStruct{10, 20} // Initializing 'x' with 10 and 'y' with 20 @assert(v1.x == 10) // Asserting 'x' is 10 @assert(v1.y == 20) // Asserting 'y' is 20 // Named initialization of fields, allowing omission of some fields var v2 = MyStruct{y: 20} // 'x' takes the default value, 'y' initialized to 20 @assert(v2.x == 1) // Asserting 'x' is the default value 1 @assert(v2.y == 20) // Asserting 'y' is 20 // Initializing using a tuple, as long as types match var v3: MyStruct = {10, 20} // Initializing using a tuple @assert(v3.x == 10) // Asserting 'x' is 10 @assert(v3.y == 20) // Asserting 'y' is 20 }

Const Structs

A struct can be defined as a constant, provided that its values can be evaluated at compile-time. This ensures that the struct's values remain immutable throughout the program's execution.

#test { struct MyStruct { x: s32 = 666 // Field 'x' initialized with default value 666 y: string = "454" // Field 'y' initialized with default value "454" } const X: MyStruct{50, "value"} // Constant struct with specified values #assert X.x == 50 // Compile-time assertion that 'x' is 50 #assert X.y == "value" // Compile-time assertion that 'y' is "value" }

Structs as Function Arguments

Functions can take a struct as an argument. This operation is performed by reference, with no copy made, which is equivalent to passing a const reference in C++.

struct Struct3 { x, y, z: s32 = 666 // Struct with default values for 'x', 'y', and 'z' } func toto(v: Struct3) { @assert(v.x == 5) // Asserting 'x' is 5 @assert(v.y == 5) // Asserting 'y' is 5 @assert(v.z == 666) // Asserting 'z' is the default value 666 } func titi(v: Struct3) { @assert(v.x == 5) // Asserting 'x' is 5 @assert(v.y == 666) // Asserting 'y' is the default value 666 } #test { // Calling with a struct literal toto(Struct3{5, 5, 666}) // Passing explicit values for 'x', 'y', and 'z' // Type can be inferred from the argument toto({5, 5, 666}) // Inferring the struct type from the provided values // Initializing only some parts of the struct, in the order of field declarations titi({5}) // Initializes 'x' only, 'y' and 'z' take default values titi({5, 666}) // Initializes 'x' and 'y', 'z' takes the default value // Initializing using named fields, with the ability to omit some fields titi({x: 5, z: 5}) // 'y' remains at its default value of 666, 'x' and 'z' are set }

Impl

Struct Methods and Constants

In Swag, structs can encapsulate methods and constants within them using the impl block. This encapsulation allows for better organization and modularity of the code, keeping the functionality related to the struct within the struct itself.

#[Swag.ExportType("methods")] // This attribute enables method reflection for this struct type struct MyStruct { x: s32 = 5 // Field 'x' with a default value of 5 y: s32 = 10 // Field 'y' with a default value of 10 z: s32 = 20 // Field 'z' with a default value of 20 } impl MyStruct { const MyConst = true // Constant defined within the struct's scope func returnTrue() => true // Function defined within the struct's scope, returns 'true' }

To access the constant or the function, use the MyStruct namespace.

#test { @assert(MyStruct.MyConst) // Accessing the constant 'MyConst' within the struct's scope @assert(MyStruct.returnTrue()) // Invoking the function 'returnTrue' within the struct's scope }

Multiple impl Blocks

Swag allows multiple impl blocks for a single struct. This feature helps organize methods logically. Within an impl block, self and Self are defined to refer to the current instance and the struct type, respectively.

impl MyStruct { // 'self' is implicitly defined as 'var self: Self', where 'Self' refers to 'MyStruct' func returnX(using self) => x // Method to return 'x', using 'self' implicitly func returnY(self) => self.y // Method to return 'y', explicitly using 'self' // 'Self' refers to the type 'MyStruct', used here as a type alias func returnZ(me: Self) => me.z // Method to return 'z', using 'me' as a type alias for 'Self' }

Method Syntax Sugar

Swag provides syntactic sugar for method definitions. If you use mtd (method) instead of func, the first parameter is implicitly using self. If you use mtd const, it becomes const using self. This feature simplifies method definitions, especially for common cases where self is required.

impl MyStruct { mtd methodReturnX() => x // Equivalent to 'func methodReturnX(using self) => x' func funcReturnX(using self) => x // Explicitly using 'self' to return 'x' } #test { var c: MyStruct @assert(c.returnX() == 5) // Calling 'returnX' method, expected to return 'x' @assert(c.methodReturnX() == 5) // Using the 'mtd' syntax, expected to return 'x' @assert(c.funcReturnX() == 5) // Explicitly using 'func' syntax, expected to return 'x' @assert(c.returnY() == 10) // Calling 'returnY' method, expected to return 'y' @assert(c.returnZ() == 20) // Calling 'returnZ' method, expected to return 'z' }

Method Reflection

To enable reflection on methods within an impl block, the struct must be annotated with #[Swag.ExportType("methods")]. By default, methods are not exported for reflection, but with this annotation, they can be accessed programmatically.

#test { // Define a type alias 'Lambda' for a function pointer taking 'MyStruct' and returning 's32' alias Lambda = func(MyStruct)->s32 var fnX: Lambda // Function pointer for 'returnX' var fnY: Lambda // Function pointer for 'returnY' var fnZ: Lambda // Function pointer for 'returnZ' // The 'typeinfo' of a struct contains a 'methods' field, which is an array of method pointers let t = MyStruct foreach p in t.methods { // The 'value' field of 'methods' contains the function pointer, which we cast to the correct type switch p.name { case "returnX": fnX = cast(Lambda) p.value // Cast the method pointer to 'Lambda' and assign to 'fnX' case "returnY": fnY = cast(Lambda) p.value // Cast the method pointer to 'Lambda' and assign to 'fnY' case "returnZ": fnZ = cast(Lambda) p.value // Cast the method pointer to 'Lambda' and assign to 'fnZ' } } // The function pointers 'fnX', 'fnY', and 'fnZ' are now callable var v: MyStruct @assert(fnX(v) == 5) // Calling the function pointer 'fnX', expected to return 5 @assert(fnY(v) == 10) // Calling the function pointer 'fnY', expected to return 10 @assert(fnZ(v) == 20) // Calling the function pointer 'fnZ', expected to return 20 }

Offset

Custom Field Layout with Swag.Offset

You can force the layout of a field within a struct using the Swag.Offset attribute. This allows you to manually specify the memory offset of a field, which can be useful for creating custom memory layouts, such as overlapping fields or sharing memory space between different fields.

#test { struct MyStruct { x: s32 // 'y' is located at the same offset as 'x', meaning they share the same memory space. // This effectively creates an overlay, where changes to one field affect the other. #[Swag.Offset("x")] y: s32 } // Although there are two fields defined, they occupy the same space, so the struct only uses 4 bytes. #assert #sizeof(MyStruct) == 4 var v = MyStruct{} v.x = 666 // Since 'x' and 'y' share the same memory space, modifying 'x' also modifies 'y'. @assert(v.y == 666) }

Using Swag.Offset for Indexed Field Access

In this example, Swag.Offset is used to reference a group of fields by index, enabling indexed access to multiple fields through a single array.

#test { struct MyStruct { x, y, z: f32 // 'idx' is an array that references the same memory locations as 'x', 'y', and 'z'. // This allows you to access 'x', 'y', and 'z' via indexed access through 'idx'. #[Swag.Offset("x")] idx: [3] f32 } var v: MyStruct v.x = 10; v.y = 20; v.z = 30 // Each index in 'idx' directly references 'x', 'y', and 'z'. @assert(v.idx[0] == v.x) @assert(v.idx[1] == v.y) @assert(v.idx[2] == v.z) }

Packing

Default Struct Packing

By default, Swag aligns struct fields similarly to the C programming language. Each field is aligned based on the size of its type, ensuring optimal memory access and usage. This default behavior can be explicitly specified using #[Swag.Pack(0)], meaning no additional packing adjustments are applied. Below is an example illustrating this default alignment strategy.

#test { struct MyStruct { x: bool // offset 0: aligned to 1 byte (no padding needed) y: s32 // offset 4: aligned to 4 bytes (3 bytes of padding before y) z: s64 // offset 8: aligned to 8 bytes (no padding needed) } #assert #offsetof(MyStruct.x) == 0 #assert #offsetof(MyStruct.y) == 4 #assert #offsetof(MyStruct.z) == 8 #assert #sizeof(MyStruct) == 16 }

Reducing Packing

Swag allows you to reduce the packing of struct fields using the #[Swag.Pack] attribute. This attribute specifies the alignment value to be applied to each field, enabling more compact struct representations. Below are examples demonstrating different levels of packing.

#test { #[Swag.Pack(1)] struct MyStruct1 { x: bool // offset 0: 1 byte (no padding) y: s32 // offset 1: 4 bytes (no padding) } #assert #offsetof(MyStruct1.x) == 0 #assert #offsetof(MyStruct1.y) == 1 #assert #sizeof(MyStruct1) == 5 #[Swag.Pack(2)] struct MyStruct2 { x: bool // offset 0: 1 byte y: s32 // offset 2: 4 bytes (1 byte of padding before y) } #assert #offsetof(MyStruct2.x) == 0 #assert #offsetof(MyStruct2.y) == 2 #assert #sizeof(MyStruct2) == 6 #[Swag.Pack(4)] struct MyStruct3 { x: bool // offset 0: 1 byte y: s64 // offset 4: 8 bytes (3 bytes of padding before y) } #assert #offsetof(MyStruct3.x) == 0 #assert #offsetof(MyStruct3.y) == 4 #assert #sizeof(MyStruct3) == 12 }

Struct Size and Alignment

The total size of a struct in Swag is always a multiple of the largest alignment value among its fields. This ensures that the struct remains correctly aligned when used within larger data structures or arrays.

#test { struct MyStruct1 { x: s32 // 4 bytes y: bool // 1 byte // 3 bytes of padding to align with s32 size } #assert #sizeof(MyStruct1) == 8 }

Enforcing Alignment with Swag.Align

Swag provides the #[Swag.Align] attribute to enforce specific alignment constraints on an entire struct. This attribute can be used to ensure that a struct's alignment meets specific hardware or performance requirements.

#test { struct MyStruct1 { x: bool // 1 byte y: bool // 1 byte } #assert #offsetof(MyStruct1.x) == 0 #assert #offsetof(MyStruct1.y) == 1 #assert #sizeof(MyStruct1) == 2 #[Swag.Align(8)] struct MyStruct2 { x: bool // 1 byte y: bool // 1 byte // 6 bytes of padding to align struct size to 8 } #assert #offsetof(MyStruct2.x) == 0 #assert #offsetof(MyStruct2.y) == 1 #assert #sizeof(MyStruct2) == 8 }

Field-Specific Alignment

Swag allows setting specific alignment values for individual struct fields using the #[Swag.Align] attribute. This enables fine-grained control over memory layout, which can be crucial for certain low-level optimizations.

#test { struct MyStruct1 { x: bool // offset 0: 1 byte #[Swag.Align(8)] y: bool // offset 8: aligned to 8 bytes (7 bytes of padding before y) } #assert #sizeof(MyStruct1) == 9 #[Swag.Align(8)] struct MyStruct2 { x: bool // offset 0: 1 byte #[Swag.Align(4)] y: bool // offset 4: aligned to 4 bytes (3 bytes of padding before y) // 3 bytes of padding to align struct size to 8 } #assert #sizeof(MyStruct2) == 8 }

Special functions

A struct in Swag can define special operations within the impl block. These operations are predefined and recognized by the compiler. This enables the ability to overload operators and customize behavior when interacting with the struct.

struct Struct { x, y: s32 // Define two properties, x and y, of type s32 (signed 32-bit integer) } alias OneType = bool // Alias for boolean type alias AnotherType = s32 // Alias for signed 32-bit integer type alias WhateverType = bool // Another alias for boolean type impl Struct { // Called whenever a variable of this struct is about to be destroyed (similar to a destructor in C++) func opDrop(using self) {} // Invoked after a raw copy operation has been performed from one value to another func opPostCopy(using self) {} // Called after a move semantic operation has been executed from one value to another func opPostMove(using self) {} // Access a value by slicing with the [low..up] notation. Returns a string or a slice. func opSlice(using self, low, up: u64)->string { return "true"; } // Access a value by its index. The index is of type OneType, and it returns a WhateverType. func opIndex(using self, index: OneType)->WhateverType { return true; } // Called when @countof is used, typically in a 'for' block, to return the count of elements func opCount(using self)->u64 { return 0; } // Called when @dataof is used, returns a pointer to the underlying data of type WhateverType func opData(using self)->*WhateverType { return null; } // Custom casting between the struct and another type, can be overloaded with different return types // Example usage: var x = cast(OneType) v #[Swag.Overload] func opCast(using self)->OneType { return true; } #[Swag.Overload] func opCast(using self)->AnotherType { return 0; } // Compare the struct value with another, can be overloaded. // Returns true if equal, otherwise false. Used in '==', '!=' operations. #[Swag.Overload] func opEquals(using self, other: OneType)->bool { return false; } #[Swag.Overload] func opEquals(using self, other: AnotherType)->bool { return false; } // Compares the struct value with another, returns -1, 0, or 1. // Used in comparison operations like '<', '>', '<=', '>=', '<=>'. #[Swag.Overload] func opCmp(using self, other: OneType)->s32 { return 0; } #[Swag.Overload] func opCmp(using self, other: AnotherType)->s32 { return 0; } // Assigns a value to the struct, can be overloaded. // This is triggered by the '=' operator. #[Swag.Overload] func opAffect(using self, other: OneType) {} #[Swag.Overload] func opAffect(using self, other: AnotherType) {} // Assign a literal value with a specific suffix to the struct. // This is a generic function and can be overloaded. #[Swag.Overload] func(suffix: string) opAffectLiteral(using self, value: OneType) {} #[Swag.Overload] func(suffix: string) opAffectLiteral(using self, value: AnotherType) {} // Assign a value to a specific index in the struct. // Can be overloaded, used by '[] =' syntax. #[Swag.Overload] func opIndexAffect(using self, index: OneType, value: OneType) {} #[Swag.Overload] func opIndexAffect(using self, index: OneType, value: AnotherType) {} // Perform a binary operation, with 'op' representing the operator as a string. // This is a generic function that can be overloaded. // Examples include '+', '-', '*', '/', '%', '|', '&', '^', '<<', '>>' #[Swag.Overload] func(op: string) opBinary(using self, other: OneType)->Self { return {1, 2}; } #[Swag.Overload] func(op: string) opBinary(using self, other: AnotherType)->Self { return {1, 2}; } // Perform a unary operation, with 'op' representing the operator as a string. // This is a generic function. // Examples include '!', '-', '~' func(op: string) opUnary(using self)->Self { return {1, 2}; } // Perform an assignment operation, with 'op' representing the operator as a string. // This is a generic function that can be overloaded. // Examples include '+=', '-=', '*=', '/=', '%=', '|=', '&=', '^=', '<<=', '>>=' #[Swag.Overload] func(op: string) opAssign(using self, other: OneType) {} #[Swag.Overload] func(op: string) opAssign(using self, other: AnotherType) {} // Assign a value to an indexed position with an operator, // 'op' represents the operator as a string. Can be overloaded. #[Swag.Overload] func(op: string) opIndexAssign(using self, index: OneType, value: OneType) {} #[Swag.Overload] func(op: string) opIndexAssign(using self, index: OneType, value: AnotherType) {} // Called in a 'foreach' block to iterate over the struct's elements. // Multiple versions can be defined by adding a name after 'opVisit'. #[Swag.Macro] { func(ptr: bool, back: bool) opVisit(using self, stmt: code) {} func(ptr: bool, back: bool) opVisitWhatever(using self, stmt: code) {} func(ptr: bool, back: bool) opVisitAnother(using self, stmt: code) {} } }

Custom assignment

Custom Assignment Behavior with opAffect

The opAffect method in Swag allows you to define custom assignment behaviors for your struct using the = operator. By overloading opAffect, you can handle assignments of different types, enabling you to specify how your struct should behave when different types of values are assigned.

struct Struct { x, y, z: s32 = 666 // Fields 'x', 'y', and 'z' with a default value of 666 } impl Struct { // Overloaded `opAffect` to handle assignments of type `s32` #[Swag.Overload] mtd opAffect(value: s32) { x, y = value // Assign the provided `value` to both 'x' and 'y' } // Overloaded `opAffect` to handle assignments of type `bool` #[Swag.Overload] mtd opAffect(value: bool) { x, y = value ? 1 : 0 // Assign 1 if `value` is true, otherwise 0, to both 'x' and 'y' } } #test { // Initialize 'v' and invoke `opAffect(s32)` with the value '4' var v: Struct = 4's32 @assert(v.x == 4) // 'x' is set to 4 @assert(v.y == 4) // 'y' is set to 4 @assert(v.z == 666) // 'z' remains at its default value since `opAffect` doesn't modify it // Initialize 'v1' and invoke `opAffect(bool)` with 'true' var v1: Struct = true @assert(v1.x == 1) // 'x' is set to 1 because 'true' was assigned @assert(v1.y == 1) // 'y' is also set to 1 because 'true' was assigned // Assign 'false' to 'v1', triggering `opAffect(bool)` v1 = false @assert(v1.x == 0) // 'x' is set to 0 (false) @assert(v1.y == 0) // 'y' is set to 0 (false) }

Optimizing Initialization with Swag.Complete

When opAffect fully initializes the struct, you can annotate it with #[Swag.Complete]. This prevents the struct from being initialized with default values before the assignment, enhancing performance by avoiding redundant assignments.

impl Struct { // Mark `opAffect` with `Swag.Complete` to ensure only one initialization step #[Swag.Complete, Swag.Overload] mtd opAffect(value: u64) { x, y, z = cast(s32) value // Convert `value` from u64 to s32 and assign it to 'x', 'y', and 'z' } // Implicit conversion example for u16 assignments #[Swag.Implicit, Swag.Overload] mtd opAffect(value: u16) { x, y = cast(s32) value // Convert `value` from u16 to s32 and assign it to 'x' and 'y' } }

In this case, the struct v is directly initialized by opAffect(u64), skipping the default initialization step. This optimization leads to more efficient code by reducing unnecessary initializations.

#test { var v: Struct = 2'u64 @assert(v.x == 2) // 'x' is directly set to 2 @assert(v.y == 2) // 'y' is directly set to 2 @assert(v.z == 2) // 'z' is directly set to 2 }

Handling Function Arguments and Automatic Conversion

By default, function arguments do not automatically undergo conversion via opAffect. Explicit casting is necessary unless Swag.Implicit is used to allow automatic conversion.

#test { func toto(v: Struct) { @assert(v.x == 5) @assert(v.y == 5) @assert(v.z == 666) // 'z' remains unchanged } func titi(v: Struct) { @assert(v.y == 666) // 'y' remains at its default value } // Explicit cast required to invoke `opAffect(s32)` toto(cast(Struct) 5's32) // With `#[Swag.Implicit]`, casting is not required, and automatic conversion occurs toto(5'u16) // Implicitly calls `opAffect(u16)` }

Using opAffect in Constant Expressions

To use opAffect in a context where the struct needs to be a constant, you can mark the method with #[Swag.ConstExpr]. This allows the struct to be initialized at compile-time via opAffect.

struct Vector2 { x, y: f32 // Fields of type f32 representing coordinates } impl Vector2 { // Enables the use of `opAffect(f32)` for constant initialization #[Swag.ConstExpr] mtd opAffect(one: f32) { x, y = one // Assign the same value to both 'x' and 'y' } } // Using `opAffect(f32)` to initialize a compile-time constant const One: Vector2 = 1.0 #assert One.x == 1.0 #assert One.y == 1.0

Custom loop

struct MyStruct {}

Implementing opCount for Iteration

The opCount method in Swag allows you to specify the number of iterations when looping over an instance of a struct. By defining this method, you can control how many times a loop executes, effectively treating the struct as an iterable object.

impl MyStruct { // Define 'opCount' to return the number of iterations for loops involving this struct. // Returns 4 as the count value, meaning any loop over this struct will run 4 times. mtd opCount() => 4'u64 }

With opCount defined, an instance of MyStruct can be looped over similarly to an array or other iterable types. This allows you to use the struct in a loop context, where the loop will execute a number of times based on the value returned by opCount.

#test { var v = MyStruct{} // '@countof' utilizes 'opCount' to determine the number of elements (iterations) @assert(@countof(v) == 4) // Verifies that the struct is considered to have 4 elements // Loop through the struct 'v', with the loop running as many times as 'opCount' returns var cpt = 0 for v: cpt += 1 // Increment the counter each iteration @assert(cpt == 4) // Ensure the loop ran 4 times, as specified by 'opCount' }

Custom iteration

struct MyStruct { x: s32 = 10 // Field 'x' initialized with default value 10 y: s32 = 20 // Field 'y' initialized with default value 20 z: s32 = 30 // Field 'z' initialized with default value 30 }

Introduction to opVisit

opVisit is a highly flexible macro used for iterating over the elements in a struct. This macro is not confined to just the struct's fields; it can also be used to traverse any data the struct contains, such as elements in dynamic arrays, internal buffers, or complex object graphs.

The #[Swag.Macro] attribute is mandatory when using opVisit. It defines the macro's behavior, allowing it to be integrated seamlessly into the codebase.

opVisit is a generic function that accepts two compile-time boolean parameters:

  • ptr: When set to true, elements are visited by pointer (address), enabling reference-based operations.
  • back: When set to true, elements are visited in reverse order (from last to first).
impl MyStruct { #[Swag.Macro] func(ptr: bool, back: bool) opVisit(self, stmt: code) { // The `ptr` and `back` parameters offer flexibility, allowing for reference-based // or reverse-order iterations. #if ptr: #error "Visiting by pointer is not supported in this example." #if back: #error "Reverse visiting is not supported in this example." // Example of visiting the fields of `MyStruct`. This demonstrates a common use case of `opVisit`. for idx in 3 { // The `#macro` directive ensures that the code is injected into the caller's scope. #macro { // `#alias0` is used to represent the current value being visited. var #alias0: s32 = undefined // Access the appropriate field based on the current `idx`. switch #up idx { case 0: #alias0 = #up self.x // Accessing field 'x' case 1: #alias0 = #up self.y // Accessing field 'y' case 2: #alias0 = #up self.z // Accessing field 'z' } // `#alias1` holds the current index of the iteration. var #alias1 = #index // Insert user-defined logic from the calling scope. #inject #up stmt } } } }

Iterating Over Struct Fields

This example demonstrates one way to use opVisit for iterating over the fields of a struct. The same approach can be extended to foreach more complex data structures.

#test { var myStruct = MyStruct{} var cpt = 0 // Visit each field of `MyStruct` in declaration order. // `v` corresponds to the value (i.e., #alias0). // `i` corresponds to the index (i.e., #alias1). foreach v, i in myStruct { switch i { case 0: @assert(v == 10) case 1: @assert(v == 20) case 2: @assert(v == 30) } cpt += 1 } @assert(cpt == 3) }

Extending opVisit: Reverse Order Iteration

You can also implement different versions of opVisit to handle other data structures. For instance, you may want to foreach the fields in reverse order.

impl MyStruct { #[Swag.Macro] mtd(ptr: bool, back: bool) opVisitReverse(stmt: code) { // In this version, we foreach the fields in reverse order. for idx in 3 { #macro { var #alias0: s32 = undefined switch #up idx { case 0: #alias0 = #up self.z // Accessing field 'z' in reverse order case 1: #alias0 = #up self.y // Accessing field 'y' in reverse order case 2: #alias0 = #up self.x // Accessing field 'x' in reverse order } var #alias1 = #index #inject #up stmt } } } }

Reverse Order Iteration

The opVisitReverse variant allows us to foreach the struct's fields in reverse order, providing flexibility depending on the needs of your application.

#test { var myStruct = MyStruct{} var cpt = 0 // Call the variant `opVisitReverse` to iterate over the fields in reverse order. foreach<Reverse> v, i in myStruct { switch i { case 0: @assert(v == 30) case 1: @assert(v == 20) case 2: @assert(v == 10) } cpt += 1 } @assert(cpt == 3) }

Visiting Elements in Dynamic Arrays

Beyond struct fields, opVisit can be designed to foreach elements in dynamic arrays, buffers, or other types of data. The flexibility of opVisit means it can adapt to whatever data structure the struct holds.

For example, consider a struct with a slice:

struct SliceStruct { buffer: [] s32 = [1, 2, 3, 4, 5] // A dynamic array (slice) initialized with values }

Custom opVisit for Dynamic Arrays

You could define an opVisit that iterates over the elements of the buffer rather than the struct's fields.

impl SliceStruct { #[Swag.Macro] func(ptr: bool, back: bool) opVisit(self, stmt: code) { for idx in @countof(self.buffer) { #macro { // #alias0 represents the value of the current buffer element. var #alias0 = #up self.buffer[#up idx] // #alias1 represents the current index. var #alias1 = #index // Insert the user-provided logic from the caller. #inject #up stmt } } } }

Iterating Over a Dynamic Array

This example shows how to foreach each element in a dynamic array (slice) and perform operations such as summing the elements.

#test { var arrStruct = SliceStruct{} var sum = 0 // Visit each element in the dynamic array buffer. foreach v, i in arrStruct { sum += v } @assert(sum == 1 + 2 + 3 + 4 + 5) // Ensuring the sum of the elements is correct }

Custom copy and move

Swag supports both copy and move semantics for structures. In this example, we demonstrate these concepts using a Vector3 struct. Although a Vector3 struct typically wouldn't require move semantics (as it doesn't involve heap allocation), this example serves to illustrate how these features are implemented and can be utilized within the Swag language.

struct Vector3 { x, y, z: s32 = 666 } impl Vector3 { // This method is invoked following a copy operation. // It exemplifies "copy semantics" and can be customized for specific behaviors post-copy. mtd opPostCopy() { x, y, z += 1 // Increment all fields by 1 to signify a copy operation has occurred. } // This method is invoked following a move operation. // It represents "move semantics" and can be customized for specific behaviors post-move. mtd opPostMove() { x, y, z += 2 // Increment all fields by 2 to signify a move operation has occurred. } // This method is invoked when an object is about to be destroyed. // While `Vector3` does not manage resources, this is where resource cleanup would typically occur, such as releasing heap-allocated memory. mtd opDrop() {} } #test { var a = Vector3{} // Default initialization of `a`. var b = Vector3{100, 200, 300} // Custom initialization of `b`. // Copy Semantics (default behavior): // 1. If 'a' already holds a value, 'opDrop' on 'a' is invoked. // 2. 'b' is copied to 'a'. // 3. 'opPostCopy' on 'a' is invoked to finalize the copy operation. a = b @assert(a.x == 101) // +1 due to 'opPostCopy'. @assert(a.y == 201) @assert(a.z == 301) // Move Semantics: // The `#move` modifier triggers move semantics: // 1. 'opDrop' on 'a' is invoked (if it exists). // 2. 'b' is moved to 'a'. // 3. 'opPostMove' on 'a' is invoked to finalize the move operation. // 4. 'b' is reinitialized to default values (666) if 'opDrop' exists. a = #move b @assert(a.x == 102) // +2 due to 'opPostMove'. @assert(a.y == 202) @assert(a.z == 302) // Post-move, 'b' is reinitialized to its default values (666) as 'opDrop' is present. @assert(b.x == 666) // The `#nodrop` modifier bypasses the initial 'opDrop' invocation. // Use this when 'a' is in an undefined state and does not require cleanup. a = #nodrop b // Copy 'b' to 'a' without invoking 'opDrop' on 'a' first. a = #nodrop #move b // Move 'b' to 'a' without invoking 'opDrop' on 'a' first. // The `#moveraw` modifier prevents the reinitialization of 'b' after the move. // This approach is risky and should be employed only when certain that 'b' won't be dropped or // when reinitialization is handled manually. a = #moveraw b // Move 'b' to 'a' without resetting 'b'. a = #nodrop #moveraw b // Move 'b' to 'a' without invoking 'opDrop' on 'a' first and without resetting 'b'. }

Move Semantics in Functions

Move semantics can be explicitly indicated in function parameters by utilizing && instead of &.

#test { // This variant of 'assign' takes ownership of 'from' by moving its contents into 'assignTo'. // The 'moveref' keyword informs the compiler that this version of 'assign' assumes ownership of 'from'. #[Swag.Overload] func assign(assignTo: &Vector3, from: &&Vector3) { assignTo = #move from // Move 'from' into 'assignTo'. } // This variant of 'assign' performs a copy instead of a move. // 'from' remains unchanged and is passed by value. #[Swag.Overload] func assign(assignTo: &Vector3, from: Vector3) { assignTo = from // Copy 'from' into 'assignTo'. } var a = Vector3{1, 2, 3} // Initialize 'a'. var b: Vector3 // Declare 'b'. // Invoke the copy version of 'assign'. assign(&b, a) @assert(b.x == 2 and b.y == 3 and b.z == 4) // +1 on each field due to 'opPostCopy'. @assert(a.x == 1 and a.y == 2 and a.z == 3) // 'a' remains unchanged. // Invoke the move version of 'assign' using 'moveref'. assign(&b, moveref &a) @assert(b.x == 3 and b.y == 4 and b.z == 5) // +2 on each field due to 'opPostMove'. @assert(a.x == 666 and a.y == 666 and a.z == 666) // 'a' is reset to default values post-move. }

Custom literals

User-Defined Literals

User-defined literals allow you to extend the meaning of literals in the language, enabling the creation of custom types that can be initialized directly using literal values with specific suffixes. This feature is particularly useful for defining types like Duration, where different units of time (e.g., seconds, milliseconds, minutes) can be intuitively represented and manipulated.

Literal Suffixes

A literal suffix is a string of characters that immediately follows a numeric literal to specify a particular unit or type. For example, in 4'ms, 'ms' is the suffix indicating that the value is in milliseconds.

To define user-defined literals, you typically provide:

  1. A structure representing the custom type (e.g., Duration).
  2. Methods that handle the conversion of the literal values based on the suffix provided.

Defining a Custom Type: Duration

The Duration type is a struct designed to represent time intervals, internally stored in seconds. The type allows initialization through various time units like seconds, milliseconds, minutes, and hours.

// Represents a delay, expressed in seconds. struct Duration { timeInSeconds: f32 // The duration in seconds } impl Duration { // Method to initialize Duration using milliseconds directly (without suffix) #[Swag.ConstExpr, Swag.Implicit, Swag.Inline] mtd opAffect(valueMs: s32) { timeInSeconds = valueMs / 1000.0 } }

To convert a given value and suffix to another value, you will use the opAffectLiteral special function.

impl Duration { // Method to handle user-defined literals with a suffix (e.g., 5's, 500'ms) #[Swag.ConstExpr, Swag.Implicit, Swag.Inline] mtd(suffix: string) opAffectLiteral(value: s32) { // This method is triggered when a literal with a suffix is used. // The `suffix` parameter indicates the unit of the literal. // The `value` parameter is the numeric part of the literal. // Check if the suffix is 's' (seconds) #if suffix == "s": // Directly assign the value as seconds timeInSeconds = value // Check if the suffix is 'ms' (milliseconds) #elif suffix == "ms": // Convert milliseconds to seconds and assign timeInSeconds = value / 1000.0 // Check if the suffix is 'min' (minutes) #elif suffix == "min": // Convert minutes to seconds (1 min = 60 seconds) timeInSeconds = value * 60.0 // Check if the suffix is 'h' (hours) #elif suffix == "h": // Convert hours to seconds (1 hour = 3600 seconds) timeInSeconds = value * 3600.0 // Handle unsupported or invalid suffixes #else: // Raise a compile-time error if an invalid suffix is encountered #error "invalid duration literal suffix '" ++ suffix ++ "'" } }

You can then use the suffix right after the literal value, as long as the type Duration is specified.

func toto(x: Duration) {} #test { let delay1: Duration = 5's // Represents 5 seconds let delay2: Duration = 500'ms // Represents 500 milliseconds let delay3: Duration = 2'min // Represents 2 minutes let delay4: Duration = 1'h // Represents 1 hour // Use the `Duration` type in functions toto(5'ms) toto(100'h) }

Interface

Interfaces in Swag are virtual tables (a list of function pointers) that can be associated with a struct.

Unlike C++, the virtual table is not embedded within the struct. It is a separate object. This allows for implementing an interface for a given struct without altering the struct's definition.

struct Point2 { x, y: f32 // Represents the coordinates of a point in a 2D space } struct Point3 { x, y, z: f32 // Represents the coordinates of a point in a 3D space }

Interface Declaration

Here we declare an interface IReset, with two functions set and reset.

interface IReset { // The first parameter must be 'self' func set(self, val: f32); // You can also use the 'mtd' declaration to avoid specifying the 'self' yourself mtd reset(); }

Implementing an Interface

You can implement an interface for any given struct with impl and for. For example, here we implement interface IReset for struct Point2.

impl IReset for Point2 { // You must add 'impl' to indicate that you want to implement a function of the interface. mtd impl set(val: f32) { x = val // Set x to the given value y = val + 1 // Set y to the value incremented by 1 } // Don't forget that 'mtd' is just syntactic sugar. 'func' still works. func impl reset(self) { self.x, self.y = 0 // Reset x and y to 0 } // Note that you can also declare 'normal' functions or methods in an 'impl' block. mtd myOtherMethod() {} // Example of an additional method }

Implementing the Interface for Another Struct

Similarly, we implement the IReset interface for struct Point3.

impl IReset for Point3 { mtd impl set(val: f32) { x = val // Set x to the given value y = val + 1 // Set y to the value incremented by 1 z = val + 2 // Set z to the value incremented by 2 } mtd impl reset() { x, y, z = 0 // Reset x, y, and z to 0 } }

Using the Interface

We can then use these interfaces on either Point2 or Point3.

#test { var pt2: Point2 var pt3: Point3 // To get the interface associated with a given struct, use the 'cast' operator. // If the compiler does not find the corresponding implementation, it will raise an error. var itf = cast(IReset) pt2 itf.set(10) @assert(pt2.x == 10) @assert(pt2.y == 10 + 1) itf = cast(IReset) pt3 itf.set(10) @assert(pt3.x == 10) @assert(pt3.y == 10 + 1) @assert(pt3.z == 10 + 2) itf.reset() @assert(pt3.x == 0 and pt3.y == 0 and pt3.z == 0) }

Accessing Interface Methods Directly

You can also access all functions declared in an interface implementation block for a given struct with a normal call. They are located in a dedicated scope.

#test { var pt2: Point2 var pt3: Point3 // The scope where all functions are located has the same name as the interface. pt2.IReset.set(10) pt2.IReset.reset() pt3.IReset.set(10) pt3.IReset.reset() }

Interface as a Type

An interface is a real type, with a size equivalent to 2 pointers: a pointer to the object and a pointer to the virtual table.

#test { var pt2: Point2 var pt3: Point3 var itf = cast(IReset) pt2 #assert #sizeof(itf) == 2 * #sizeof(*void) // You can retrieve the concrete type associated with an interface instance with '@kindof'. itf = cast(IReset) pt2 @assert(@kindof(itf) == Point2) itf = cast(IReset) pt3 @assert(@kindof(itf) == Point3) // You can retrieve the concrete data associated with an interface instance with '@dataof' itf = cast(IReset) pt2 @assert(@dataof(itf) == &pt2) itf = cast(IReset) pt3 @assert(@dataof(itf) == &pt3) }

Default Implementation in Interfaces

When you declare an interface, you can define a default implementation for each function. If a struct does not redefine the function, then the default implementation will be called instead.

Just declare a body in the interface function to provide a default implementation.

interface ITest { mtd isImplemented()->bool { return false; } // Default implementation returns false }

Here we define a specific version of isImplemented for Point2, and no specific implementation for Point3.

impl ITest for Point2 { mtd impl isImplemented()->bool { return true; } // Override to return true for Point2 } impl ITest for Point3 { // No override, so the default implementation will be used. }

For Point3, isImplemented() will return false because this is the default implementation.

#test { var v2: Point2 var v3: Point3 // 'isImplemented' has been redefined, and will return 'true' for Point2. let i2 = cast(ITest) v2 @assert(i2.isImplemented()) // 'isImplemented' is not redefined, it will return false for Point3. let i3 = cast(ITest) v3 @assert(!i3.isImplemented()) }

Functions

Declaration

Introduction to Function Declarations

A function declaration typically starts with the func keyword, followed by the function name and a pair of parentheses. If no parameters are needed, the parentheses are empty.

#[Swag.Overload] func toto() {}

Returning Values from Functions

If a function is intended to return a value, use -> followed by the return type. The function body must include a return statement with the corresponding value.

func toto1() -> s32 { return 0 // Returns an integer value of 0 }

Inferring Return Types

For simple expressions, the return type can be inferred automatically using the => operator instead of explicitly declaring it with ->. This reduces verbosity in cases where the type is obvious.

func sum(x, y: s32) => x + y // Return type is inferred as s32

Shorter Syntax for Functions Without Return Values

When a function does not return a value, a concise syntax can be used. Instead of a full function body, a single expression can be provided after =.

func print(val: string) = @print(val) // Prints the given string value

Defining Parameters in Functions

Function parameters are defined within parentheses following the function name. Each parameter is declared with a name and type, separated by a colon. In this example, two parameters x and y of type s32, and an additional unused parameter of type f32 are defined.

func sum1(x, y: s32, unused: f32) -> s32 { return x + y // Returns the sum of x and y }

Using Default Parameter Values

Parameters can be assigned default values. If the caller does not provide a value for such a parameter, the default value is used.

func sum2(x, y: s32, unused: f32 = 666) -> s32 { return x + y // Returns the sum of x and y }

Inferred Parameter Types

If a parameter has a default value, its type can be inferred from the value provided. In this example, x and y are inferred as f32 due to the 0.0 literal.

func sum3(x, y = 0.0) { #assert #typeof(x) == f32 // Asserts that x is of type f32 #assert #typeof(y) == f32 // Asserts that y is of type f32 }

Overloading Functions

Swag allows function overloading, where multiple functions can share the same name but differ in their parameter types or counts.

enum Values { A, B } #[Swag.Overload] func toto(x: s32, y = Values.A) { #assert #typeof(y) == Values // Asserts that y is of type Values }

Nested Functions

Functions can be nested within other functions to provide encapsulation and organize code. These nested functions are not closures but are limited to the scope in which they are declared.

#test { func sub(x, y: s32) => x - y // Defines a nested function let x = sub(5, 2) // Calls the nested function @assert(x == 3) // Asserts the result of the function call }

Named Parameters and Parameter Order

Swag supports named parameters, allowing you to specify arguments in any order when calling a function. This can enhance code readability and flexibility.

#test { func sub(x, y: s32) => x - y // Defines a function for subtraction { let x1 = sub(x: 5, y: 2) // Calls with named parameters @assert(x1 == 3) // Asserts that the result is correct let x2 = sub(y: 5, x: 2) // Calls with parameters in reversed order @assert(x2 == -3) // Asserts that the result is correct } { func returnMe(x, y: s32 = 0) => x + y * 2 // Defines a function with a default parameter @assert(returnMe(x: 10) == 10) // Calls with one parameter @assert(returnMe(y: 10) == 20) // Calls with the second parameter only } }

Returning Multiple Values with Anonymous Structs

Anonymous structs provide a convenient way to return multiple values from a function. This method facilitates accessing returned data either by destructuring or directly from the struct.

#test { func myFunction() -> { x, y: f32 } { return {1.0, 2.0} // Returns a struct with two float values } var result = myFunction() // Stores the result in a variable @assert(result.item0 == 1.0) // Asserts the first item @assert(result.item1 == 2.0) // Asserts the second item let (x, y) = myFunction() // Destructures the result into variables @assert(x == 1.0) // Asserts the value of x @assert(y == 2.0) // Asserts the value of y let (z, w) = myFunction() // Another destructuring example @assert(z == 1.0) // Asserts the value of z @assert(w == 2.0) // Asserts the value of w }

Lambda

Introduction to Lambdas in Swag

A lambda in Swag is a pointer to a function. This allows functions to be stored in variables, passed as arguments, or returned from other functions, making them versatile in functional programming.

#test { func myFunction0() {} func myFunction1(x: s32) => x * x // 'ptr0' is a pointer to a function that takes no parameters and returns nothing. let ptr0: func() = &myFunction0 ptr0() // Call the function through the pointer // The type of 'ptr1' is inferred from 'myFunction1'. let ptr1 = &myFunction1 @assert(myFunction1(2) == 4) @assert(ptr1(2) == 4) // Call the function using the pointer }

Null Lambdas

A lambda can also be null, indicating that it does not point to any function. This is useful for optional callbacks or deferred initialization of function pointers.

#test { var lambda: func()->bool @assert(lambda == null) // Confirm that the lambda is initially null }

Using Lambdas as Function Parameters

Lambdas can be passed as parameters, enabling higher-order functions where other functions are executed dynamically based on input.

#test { alias Callback = func(s32)->s32 func toDo(value: s32, ptr: Callback)->s32 => ptr(value) // Execute the lambda with the given value func square(x: s32) => x * x @assert(toDo(4, &square) == 16) // Pass the square function as a callback }

Anonymous Functions

Anonymous functions, or function literals, can be defined directly in your code without the need for a named identifier, making them convenient for quick, inline functionality.

#test { var cb = func(x: s32)->s32 => x * x // Define an anonymous function that squares a number @assert(cb(4) == 16) cb = func(x: s32)->s32 => x * x * x // Reassign to an anonymous function that cubes a number @assert(cb(4) == 64) }

Passing Anonymous Functions as Parameters

Anonymous functions can be passed directly as arguments to other functions, without first being assigned to variables. This enables concise and flexible code.

#test { alias Callback = func(s32)->s32 func toDo(value: s32, ptr: Callback)->s32 => ptr(value) @assert(toDo(4, func(x: s32) => x * x) == 16) // Passing anonymous functions as arguments @assert(toDo(4, func(x: s32) => x + x) == 8) @assert(toDo(4, func(x: s32)->s32 { return x - x; }) == 0) }

Inferred Parameter Types in Anonymous Functions

Swag allows for inferred parameter types in anonymous functions, resulting in cleaner and more concise code, especially when the type can be unambiguously deduced from context.

#test { alias Callback = func(s32)->s32 func toDo(value: s32, ptr: Callback)->s32 => ptr(value) @assert(toDo(4, func(x) => x * x) == 16) // The type of 'x' is inferred from context @assert(toDo(4, func(x) => x + x) == 8) @assert(toDo(4, func(x) { return x - x; }) == 0) }

Omitting Types When Assigning Lambdas

When assigning a lambda to a variable, parameter and return types can be omitted if they are inferable from the variable's type. This reduces verbosity while maintaining type safety.

#test { var fct: func(s32, s32)->bool fct = func(x, y) => x == y // Assign a lambda with inferred parameter types @assert(fct(10, 10)) // Assign a lambda with a block body fct = func(x, y) { return x != y } @assert(fct(20, 120)) }

Lambdas with Default Parameter Values

Lambdas in Swag can have default parameter values, enhancing their flexibility and allowing them to adapt to various contexts with minimal adjustments.

#test { { let x = func(val = true) { @assert(val == true) } x() // Call the lambda without arguments } { var x: func(val: bool = true) x = func(val) { @assert(val == true) } x() // Call with the default value x(true) // Explicitly pass the value } }

Closure

Introduction to Closures in Swag

Swag supports a limited implementation of the closure concept. A closure allows capturing variables from its surrounding scope. Swag currently permits capturing up to 48 bytes, ensuring no hidden allocations. However, only simple variables (i.e., variables without custom behaviors such as opDrop, opPostCopy, or opPostMove) are eligible for capture.

Declaring a Closure

A closure is declared similarly to a lambda, with captured variables specified between |...| before the function parameters. For a type, the syntax is func||(...).

#test { let a = 125 // Initialize variable 'a' with a value of 125. let b = 521 // Initialize variable 'b' with a value of 521. // Capture 'a' and 'b' by value, meaning copies of these variables are captured. let fct: func||() = func|a, b|() { // Inside the func, the captured values 'a' and 'b' are accessible. @assert(a == 125) // Verify that the captured 'a' equals 125. @assert(b == 521) // Verify that the captured 'b' equals 521. } fct() // Invoke the func. }

Capturing Variables by Reference

Variables can also be captured by reference using &. By default, variables are captured by value.

#test { var a = 125 // Declare a mutable variable 'a' with an initial value of 125. // Capture 'a' by reference, meaning changes to 'a' inside the func will affect 'a' outside the func. let fct: func||() = func|&a|() { a += 1 // Modify the captured variable 'a' by incrementing it. } fct() // Invoke the func, which increments 'a' by 1. @assert(a == 126) // Check that 'a' has been incremented to 126. fct() // Invoke the func again, further incrementing 'a'. @assert(a == 127) // Check that 'a' has been incremented to 127. }

Assigning Lambdas to Closure Variables

A closure variable can also hold a standard lambda (without capture). This provides flexibility in assigning different types of functions to the same variable.

#test { var fct: func||(s32, s32) -> s32 // Declare a func variable that takes two integers and returns an integer. // Assign a simple lambda that adds two integers to the func variable 'fct'. fct = func(x, y) => x + y @assert(fct(1, 2) == 3) // Test the lambda by passing values 1 and 2, expecting the result to be 3. }

Capturing Complex Types

You can capture arrays, structs, slices, etc., as long as they fit within the maximum capture size and the struct is a Plain Old Data (POD) type.

#test { var x = [1, 2, 3] // Declare and initialize an array 'x' of integers. var fct: func||(s32) -> s32 // Declare a closure variable that takes an integer and returns an integer. // Capture the array 'x' by value (a copy of the array is made). fct = func|x|(toAdd) { var res = 0 // Initialize a result variable 'res' to accumulate the sum. foreach v in x: // Iterate over the captured array 'x' and sum its elements. res += v res += toAdd // Add the 'toAdd' parameter to the sum. return res } let result = fct(4) // Invoke the closure with the value 4. @assert(result == 1 + 2 + 3 + 4) // Verify the result is the sum of 1 + 2 + 3 + 4. }

Modifying Captured Variables

Captured variables are mutable and part of the closure, allowing you to modify them. This enables the creation of stateful functions.

#test { // A function that returns a closure, which increments a captured variable 'x' each time it's called. func getInc() -> func||() -> s32 { let x = 10 // Initialize 'x' with 10. // Return a closure that captures 'x' by value. return func|x|() -> s32 { x += 1 // Increment the captured 'x' and return its new value. return x } } let fct = getInc() // Obtain the closure returned by 'getInc'. @assert(fct() == 11) // First call, 'x' becomes 11. @assert(fct() == 12) // Second call, 'x' becomes 12. @assert(fct() == 13) // Third call, 'x' becomes 13. }

Mixin

Introduction to Swag Mixins

A mixin in Swag is declared similarly to a function but with the attribute #[Swag.Mixin]. Mixins allow injecting code into the caller's scope, manipulating variables, or executing code as if it were part of that scope. This documentation provides an overview of Swag Mixins with various examples, demonstrating their flexibility and use cases.

#test { #[Swag.Mixin] func myMixin() {} // Declaring a basic mixin with no functionality }

Basic Example of a Mixin

A mixin function can directly modify variables in the caller's scope. In this example, the mixin increments a variable a by 1 each time it is called.

#test { #[Swag.Mixin] func myMixin() { a += 1 // Incrementing 'a' by 1 } var a = 0 myMixin() // Equivalent to writing 'a += 1' directly in the scope myMixin() // Again, equivalent to 'a += 1' @assert(a == 2) // Verifies that 'a' has been incremented twice }

Mixins with Parameters

Mixins behave like functions, allowing parameters, default values, and even return values. This example demonstrates a mixin with an increment parameter that defaults to 1.

#test { #[Swag.Mixin] func myMixin(increment: s32 = 1) { a += increment // Incrementing 'a' by the value of 'increment' } var a = 0 myMixin() // Equivalent to 'a += 1', using the default value myMixin(2) // Equivalent to 'a += 2', using the passed parameter @assert(a == 3) // Verifies that 'a' has been incremented by 1 and 2 }

Mixins with Code Blocks

A mixin can accept a special parameter of type code, representing a Swag code block defined at the call site. The mixin can execute this code block multiple times using the #inject keyword.

#test { #[Swag.Mixin] func doItTwice(what: code) { #inject what // Executing the passed code block the first time #inject what // Executing the passed code block the second time } var a = 0 doItTwice(#code { a += 1; }) // Incrementing 'a' twice through the mixin @assert(a == 2) // Verifies that 'a' was incremented twice }

Mixing Code Blocks in Separate Statements

When the last parameter of a mixin is of type code, the code can be declared in a separate statement after the mixin call. This provides a more natural syntax for passing code blocks.

#test { #[Swag.Mixin] func doItTwice(value: s32, what: code) { #inject what // Executing the passed code block the first time #inject what // Executing the passed code block the second time } var a = 0 doItTwice(4, #code { a += value; }) // Passing code block with direct syntax doItTwice(2) // Alternatively, pass the code block naturally { a += value // Incrementing 'a' by 'value' twice } @assert(a == 12) // Verifies that 'a' was incremented as expected }

Creating Aliases with Mixins

You can use the special name #alias to create a named alias for an identifier. This enables flexible manipulation of variables through mixins.

#test { #[Swag.Mixin] func inc10() { #alias0 += 10 // Incrementing the aliased variable by 10 } var a, b = 0 inc10(|a|) // Use 'a' as the alias inc10(|b|) // Use 'b' as the alias @assert(a == b and b == 10) // Verifies that both 'a' and 'b' were incremented by 10 } #test { #[Swag.Mixin] func setVar(value: s32) { let #alias0 = value // Setting the aliased variable to 'value' } setVar(|a| 10) // Set 'a' to 10 setVar(|b| 20) // Set 'b' to 20 @assert(a == 10) // Verifies that 'a' was set to 10 @assert(b == 20) // Verifies that 'b' was set to 20 setVar(30) // No alias provided, so default alias '#alias0' is used @assert(#alias0 == 30) // Verifies that '#alias0' was set to 30 }

Unique Variable Names with #mix?

Mixins can declare special variables named #mix?. These variables receive a unique name each time the mixin is invoked, preventing naming conflicts and allowing multiple mixin invocations in the same scope.

#test { var total: s32 #[Swag.Mixin] func toScope() { var #mix0: s32 = 1 // Declaring a unique variable named '#mix0' total += #mix0 // Adding the value of '#mix0' to 'total' } toScope() // First invocation of the mixin toScope() // Second invocation toScope() // Third invocation @assert(total == 3) // Verifies that 'total' is the sum of all mixin invocations }

Macro

Introduction to Swag Macros

Macros in Swag are defined similarly to functions, with the key distinction being the #[Swag.Macro] attribute. This attribute indicates that the function is intended to be a macro, which can be reused and expanded at compile-time.

#test { #[Swag.Macro] func myMacro() {} }

Macro Scope

Macros operate within their own scope, which is separate and isolated from the caller's scope. This is different from mixins, which share the caller's scope. The isolation provided by macros ensures that any variables defined inside the macro do not interfere with or modify variables outside the macro, preventing potential naming conflicts.

#test { #[Swag.Macro] func myMacro() { // This variable 'a' is local to the macro and does not affect or interfere // with any 'a' outside the macro. var a = 666 // 'a' is confined to the macro's scope } // Declare a variable 'a' in the outer scope and initialize it to 0. let a = 0 // Call the macro `myMacro()`. The macro defines its own 'a', but this does not // conflict with the outer 'a'. myMacro() // No conflict with the outer 'a' // Verify that the outer 'a' remains unchanged. @assert(a == 0) }

Resolving Identifiers Outside the Macro Scope

Macros typically have their own scope, which means variables inside them are isolated from the outer environment. However, using the #up keyword, you can explicitly reference and modify variables that are defined outside the macro. This allows the macro to interact with the caller's environment.

#test { #[Swag.Macro] func myMacro() { // Use `#up` to access and modify the variable `a` from the outer scope (the scope where the // macro is called). Without `#up`, `a` would be assumed to be a variable within the macro's // own scope (which might not exist). #up a += 1 // Increments the outer 'a' by 1 } // Declare a variable `a` in the outer scope var a = 0 // Call the macro `myMacro()`, which increments the outer `a` by 1 using `#up` myMacro() // `a` becomes 1 // Call the macro `myMacro()` again, which increments `a` by 1 again myMacro() // `a` becomes 2 // Verify that `a` has been incremented twice @assert(a == 2) }

Macros with Code Parameters

Macros can take code parameters, enabling you to pass and insert code blocks dynamically within the macro. This feature is similar to mixins but within the macro's scope.

#test { #[Swag.Macro] func myMacro(what: code) { #inject what // Inserts the provided code block } var a = 0 myMacro(#code { #up a += 1 // Increment 'a' within the code block }) myMacro() { #up a += 1 // Alternative way to pass the code block directly } @assert(a == 2) // Verifies that 'a' has been incremented twice }

Forcing Code into the Caller’s Scope with #macro

The #macro keyword can be used to ensure that the code within a macro operates in the caller's scope. This technique negates the need for the #up keyword when referencing the caller's variables.

#test { #[Swag.Macro] func myMacro(what: code) { var a = 666 // Declare 'a' in the macro's own scope #macro // Ensures the following code operates in the caller's scope { #inject #up what // References the caller's 'a' } } var a = 1 myMacro() { a += 2 // 'a' references the caller's 'a' due to '#macro' } @assert(a == 3) // Verifies that the caller's 'a' was incremented }

Performance Considerations with Macros

Macros in Swag can extend the language capabilities without relying on function pointers. This avoids the overhead associated with lambda calls, making macros a performance-efficient choice.

#test { #[Swag.Macro] func repeat(count: s32, what: code) { var a = 0 while a < count { #macro { var index = #up a // 'index' references 'a' from the caller's scope #inject #up what // Insert the provided code block } a += 1 } } var a = 0 repeat(5) { a += index // Sum 'index' values from the macro's loop } @assert(a == 0 + 1 + 2 + 3 + 4) // Verifies the sum after the first repeat repeat(3) { a += index // Continue summing with a new loop } @assert(a == 10 + 3) // Verifies the final sum after both repeats }

Handling break and continue in User Code with Macros

Macros allow you to customize the behavior of break and continue statements in loops. This can be particularly useful in complex nested loops where you want a break or continue statement to affect an outer loop, not just the immediate one.

By using a macro, you can define aliases for break and continue that allow you to control exactly which loop they affect.

#test { #[Swag.Macro] func repeatSquare(count: u32, what: code) { // Define a label `Up` for the scope that will allow us to break out of the outermost loop #scope ScopeTarget // Outer for: this will run `count` times for count { // Inner for: also runs `count` times for count { #macro { // Injects the user code `what` here. // The `#inject` directive replaces certain parts of the user code: // - `break` in the user code is replaced with `break to Up`, meaning it will break // out of the `Up` scope (i.e., the outer `for`). // - You can similarly redefine `continue` if needed. #inject #up what => { break = break to ScopeTarget; } } } } } // Initialize a variable `a` to 0 var a = 0 // Call the `repeatSquare` function with `count = 5` // The provided code block increments `a` and breaks when `a == 10` repeatSquare(5) { a += 1 if a == 10: // This `break` statement is replaced by `break to Up` due to the macro, // meaning it will exit the outermost `for`, not just the inner `for`. break } // Assertion to check if `a` is indeed 10 after the `for` exits @assert(a == 10) // Verifies that the loop exited when `a` reached 10 }
Another example:
#test { #[Swag.Macro] func repeatSquare(count: u32, what: code) { // Define a label `Outer` for the scope that will allow us to // break or continue from the outermost loop #scope Outer // Outer for: this will run `count` times for count { // Inner for: also runs `count` times for count { #macro { // `break` in the user code is replaced with `break to Outer`, exiting the outer loop. // `continue` is replaced with `break`, skipping to the next iteration of the inner loop. #inject #up what => { break = break to Outer; continue = break; } } } } } // Initialize a variable `a` to 0 and a variable `b` to 0 var a = 0 var b = 0 // Call the `repeatSquare` function with `count = 5` // The provided code block increments `a` and uses `continue` and `break` under certain conditions repeatSquare(5) { a += 1 // If `a` is divisible by 3, skip to the next iteration of the outer loop (both loops) if a % 3 == 0: continue // Increment `b` only if `continue` was not called b += 1 // If `a` equals 8, exit both loops if a == 8: break } // Verifies that the loop exited when `a` reached 8 @assert(a == 8) // Verifies that `b` was incremented 6 times, skipping increments when `a` was divisible by 3 @assert(b == 6) }

Using Aliases in Macros

Special variables named #alias<num> can be used within macros, similar to mixins. These aliases allow you to define and access specific variables within a macro.

#test { #[Swag.Macro] func call(v: s32, stmt: code) { let #alias0 = v // Assign 'v' to '#alias0' let #alias1 = v * 2 // Assign 'v * 2' to '#alias1' #inject stmt // Insert the provided code block } call(20) { @assert(#alias0 == 20) // Verifies '#alias0' equals 20 @assert(#alias1 == 40) // Verifies '#alias1' equals 40 } call(|x| 20) { @assert(x == 20) // 'x' is used as an alias for '#alias0' @assert(#alias1 == 40) // '#alias1' remains unchanged } call(|x, y| 20) { @assert(x == 20) // 'x' replaces '#alias0' @assert(y == 40) // 'y' replaces '#alias1' } }

Variadic parameters

Introduction to Variadic Functions

Variadic functions accept a variable number of arguments using .... This capability enables functions to handle a flexible number of parameters, making them more versatile in scenarios where the exact number of arguments is not known in advance.

#test { func myFunction(value: bool, parameters: ...) { // This function can now accept any number of additional arguments after 'value'. } myFunction(true, 4, "true", 5.6) // Passes 4, "true", and 5.6 as additional parameters. }

Working with Variadic Parameters as Slices

When a function takes a variadic parameter, the parameters variable is treated as a slice of type any. This feature allows the function to flexibly handle different types of arguments at runtime.

#test { func myFunction(parameters: ...) { // Determine the number of parameters passed. @assert(@countof(parameters) == 3) // Each parameter is initially treated as 'any' type. #assert #typeof(parameters[0]) == any #assert #typeof(parameters[1]) == any #assert #typeof(parameters[2]) == any // Use '@kindof' to determine the actual type of each parameter at runtime. @assert(@kindof(parameters[0]) == s32) @assert(@kindof(parameters[1]) == string) @assert(@kindof(parameters[2]) == f32) } myFunction(4, "true", 5.6) // Passes an integer, string, and float. }

Forcing Variadic Parameters to a Specific Type

If all variadic parameters are of the same type, you can enforce that type using type annotations. This practice makes the parameters' type explicit, ensuring they are not treated as any.

#test { func myFunction(value: bool, parameters: s32...) { // All parameters in 'parameters' must be of type 's32'. #assert #typeof(parameters[0]).name == "s32" #assert #typeof(parameters[1]).name == "s32" #assert #typeof(parameters[2]) == s32 #assert #typeof(parameters[3]) == s32 // Verify that the parameters have been passed correctly. @assert(parameters[0] == 10) @assert(parameters[1] == 20) @assert(parameters[2] == 30) @assert(parameters[3] == 40) } myFunction(true, 10, 20, 30, 40) // Passes four integers. }

Passing Variadic Parameters Between Functions

You can pass variadic parameters from one function to another, preserving their types and values. This technique is useful when you need to delegate tasks to other functions without losing the variadic nature of the arguments.

#test { func A(params: ...) { // Expecting a string and a boolean. @assert(@countof(params) == 2) @assert(@kindof(params[0]) == string) @assert(@kindof(params[1]) == bool) @assert(cast(string) params[0] == "value") @assert(cast(bool) params[1] == true) } func B(params: ...) { // Pass the variadic parameters from B to A. A(params) } B("value", true) // Passes the parameters to function A through B. }

Spreading Arrays or Slices to Variadic Parameters

You can spread the contents of an array or slice into variadic parameters using @spread. This feature is handy when you have a collection of values that you want to pass as individual arguments.

#test { func sum(params: s32...)->s32 { // Variadic parameters can be iterated as they are slices. var total = 0 foreach v in params: total += v return total } var arr = [1, 2, 3, 4] let res = sum(@spread(arr)) // Equivalent to sum(1, 2, 3, 4). @assert(res == 1 + 2 + 3 + 4) let res1 = sum(@spread(arr[1..3])) // Equivalent to sum(2, 3, 4). @assert(res1 == 2 + 3 + 4) }

Advanced Example: Combining Variadic and Non-Variadic Parameters

This example demonstrates how to combine fixed parameters with variadic parameters and use them together in a function.

private func print() { func logMessage(prefix: string, messages: ...) { // Print each message with the given prefix. foreach msg in messages { @print(prefix, " => ", cast(string) msg) } } logMessage("Error: ", "File not found", "Access denied", "Disk full") }

Example: Handling Different Types in Variadic Parameters

This example shows how to handle different types within a variadic function, such as summing integers and concatenating strings.

#test { func processParameters(params: ...)->s32 { var sum = 0 foreach p in params { switch @kindof(p) { case s32: sum += 1 case string: sum += 10 } } return sum } let result = processParameters(1, 2, "Hello, ", 3, "World!") @assert(result == 1 + 1 + 10 + 1 + 10) }

Ufcs

Introduction to Uniform Function Call Syntax (UFCS)

UFCS stands for Uniform Function Call Syntax. It allows any function to be called using the param.func() form when the first parameter of func() matches the type of param. This syntax provides a way to call static functions as if they were methods on an instance, enhancing readability and method-like behavior.

#test { func myFunc(param: bool) => param let b = false @assert(myFunc(b) == b.myFunc()) // Using UFCS to call 'myFunc' as if it were a method on 'b'. }

Static Functions as Methods

In Swag, all functions are static, meaning they are not inherently bound to instances of structs or classes. However, UFCS allows these functions to be called in a method-like style, making struct manipulation more intuitive and the code more readable.

#test { struct Point { x, y: s32 } func set(using pt: *Point, value: s32) { x, y = value } var pt: Point // Using UFCS to call 'set' as if it were a method of 'pt'. pt.set(10) @assert(pt.x == 10 and pt.y == 10) // Normal static function call. set(&pt, 20) @assert(pt.x == 20 and pt.y == 20) }

UFCS with Multiple Parameters

UFCS works seamlessly with functions that take multiple parameters, as long as the first parameter matches the type of the instance. This allows for consistent and readable function calls, even with more complex function signatures.

#test { struct Vector { x, y: f32 } func add(using vec: *Vector, dx: f32, dy: f32) { x += dx y += dy } var v: Vector // Using UFCS to call 'add' as if it were a method of 'v'. v.add(1.0, 2.0) @assert(v.x == 1.0 and v.y == 2.0) // Normal static function call. add(&v, 3.0, 4.0) @assert(v.x == 4.0 and v.y == 6.0) }

UFCS and Function Overloading

UFCS supports function overloading, where the appropriate function is chosen based on the types of the parameters provided. This feature ensures that UFCS remains versatile and applicable across a wide range of function signatures, allowing for flexible and context-appropriate behavior.

#test { struct Complex { real, imag: f32 } #[Swag.Overload] func multiply(c: *Complex, scalar: f32) { c.real *= scalar c.imag *= scalar } #[Swag.Overload] func multiply(using c: *Complex, other: *Complex) { real = (real * other.real) - (imag * other.imag) imag = (real * other.imag) + (imag * other.real) } var c1 = Complex{2.0, 3.0} var c2 = Complex{4.0, 5.0} // Using UFCS to multiply by a scalar. c1.multiply(2.0) @assert(c1.real == 4.0 and c1.imag == 6.0) // Using UFCS to multiply by another Complex number. c1.multiply(&c2) @assert(c1.real == -14.0 and c1.imag == -46.0) }

Constexpr

Swag.ConstExpr Functions

A function marked with Swag.ConstExpr can be executed at compile time by the compiler if the input values are known at that stage. This allows the function's result to be "baked" into the code, reducing runtime computation and potentially improving performance.

#[Swag.ConstExpr] func sum(x, y: f32) => x + y // This function can be executed at compile time.

Example: Compile-Time Computation

In the example below, the compiler will execute the sum function at compile time, embedding the result directly into the constant G. The value of G will be 3, computed during the compilation process, ensuring no runtime overhead for this calculation.

const G = sum(1, 2) #assert G == 3 // The result of `sum(1, 2)` is computed at compile time and verified.

Example: Using Swag.ConstExpr with Complex Expressions

Swag.ConstExpr functions can handle more complex expressions as well. The following example shows how a compile-time function can perform multiple operations and still have its result computed during compilation.

#[Swag.ConstExpr] func complexCalc(a, b, c: f32) => (a + b) * c / 2 const result = complexCalc(4, 5, 6) #assert result == 27.0 // The complex expression is evaluated at compile time.

Example: Compile-Time Execution of Array Initializations

Swag.ConstExpr functions can also be used to initialize arrays or other data structures at compile time. This example demonstrates how an array of precomputed values is created.

#[Swag.ConstExpr] func square(n: s32) => n * n const Squares = [square(1), square(2), square(3), square(4), square(5)] #assert Squares[0] == 1 #assert Squares[1] == 4 #assert Squares[2] == 9 #assert Squares[3] == 16 #assert Squares[4] == 25

Forcing Compile-Time Execution with #run

If a function is not marked with Swag.ConstExpr, but you still want to execute it at compile time, you can use the #run directive. This forces the compiler to execute the function during compilation and use the result in your code.

func mul(x, y: f32) => x * y // This is a normal function, not marked as `Swag.ConstExpr`. // The `#run` directive forces the compile-time execution of `mul(3, 6)`. const G1 = #run mul(3, 6) #assert G1 == 18 // The result of `mul(3, 6)` is computed at compile time and verified.

Example: Compile-Time Evaluation of Conditional Logic

Using Swag.ConstExpr or #run, you can evaluate conditional logic at compile time, enabling the embedding of decision results directly into your constants.

#[Swag.ConstExpr] func max(a, b: s32) => a > b ? a : b const MaxValue = max(10, 20) #assert MaxValue == 20 // The comparison is performed at compile time.

Example: Compile-Time Initialization of Structs

You can initialize complex data structures, such as structs, at compile time using Swag.ConstExpr. This ensures that the structure's values are determined before runtime.

#[Swag.ConstExpr] struct Point { x, y: s32 } #[Swag.ConstExpr] func createPoint(a, b: s32) => Point{a, b} const Origin = createPoint(1, 2) #assert Origin.x == 1 and Origin.y == 2 // The Point is initialized at compile time.

Example: Using #run with User-Defined Types

In cases where Swag.ConstExpr is not used, #run can force compile-time execution even for user-defined types like structs or enums.

struct Rectangle { width, height: s32 } func area(rect: Rectangle) => rect.width * rect.height const RectStatic = Rectangle{ 5, 10 } const RectArea = #run area(RectStatic) #assert RectArea == 50 // The area of the rectangle is computed at compile time.

Function overloading

Function Overloading with Swag.Overload

In Swag, it is possible to define multiple functions with the same name as long as their parameter signatures differ. This capability, known as function overloading, allows you to provide different implementations of a function depending on the number or types of arguments passed. To enable function overloading, the functions must be decorated with the Swag.Overload attribute. This ensures that the compiler can correctly distinguish between the different versions based on the arguments provided at the call site.

#[Swag.ConstExpr, Swag.Overload] { // Overloaded function with two parameters func sum(x, y: s32) => x + y // Sums two integers // Overloaded function with three parameters func sum(x, y, z: s32) => x + y + z // Sums three integers }

In the example above, the sum function is overloaded to handle both two-parameter and three-parameter cases. When the function is called, the compiler automatically selects the appropriate version based on the number of arguments. This allows for more flexible and intuitive code, where the same function name can be used for different operations depending on the context.

#assert sum(1, 2) == 3 // Calls the two-parameter version of `sum`, which returns 3 #assert sum(1, 2, 3) == 6 // Calls the three-parameter version of `sum`, which returns 6

Discard

Return Value Usage

In Swag, there is a strict requirement that every function's return value must be utilized. This design choice helps prevent potential bugs that can arise from accidentally ignoring the result of a function call. If a function is invoked and its return value is not used, the compiler will generate an error. This ensures that developers are consciously handling all return values, which can be critical for maintaining the correctness of the code.

#test { func sum(x, y: s32) => x + y // Uncommenting the following line would generate a compiler error, // because the return value of 'sum' is not used. // sum(2, 3) // To explicitly discard the return value when you don't need it, use 'discard' at the call site. // The return value of 'sum' is intentionally ignored here. discard sum(2, 3) }

Swag.Discardable Attribute

There are scenarios where the return value of a function may be optional or non-critical to the operation. In such cases, the function can be marked with the Swag.Discardable attribute. This attribute permits the caller to ignore the return value without triggering a compiler error, making it clear that the result is not necessarily intended to be used.

#test { #[Swag.Discardable] func mul(x, y: s32) -> s32 => x * y // It's allowed to ignore the return value of 'mul' without using 'discard'. mul(2, 4) }

Retval

The retval special type

In Swag, the retval type acts as an alias to the function's return type. This feature allows developers to handle the return value within the function in a more convenient and flexible manner. By using retval, you can define and manipulate the variable intended to be returned, without explicitly specifying its type. This abstraction simplifies code maintenance and enhances readability, especially when dealing with complex return types.

#test { func toto() -> s32 { var result: retval // 'retval' is equivalent to 's32' in this context. result = 10 // Assign a value to 'result'. return result // Return the value stored in 'result'. } @assert(toto() == 10) // The function returns 10 using the `retval` type. }

Optimizing return values

The retval type also serves as an optimization hint to the compiler. When used correctly, it allows the compiler to reference the caller's storage directly, bypassing unnecessary copies of the return value. This is particularly beneficial for functions returning large or complex types, such as structs, tuples, or arrays, where performance can be significantly improved by reducing memory operations.

#test { struct RGB { x, y, z: f64 } func getWhite() -> RGB { // Using `retval = undefined` avoids unnecessary clearing of the returned struct. var result: retval = undefined // 'retval' here is equivalent to 'RGB'. result.x = 0.5 // Assign value to the 'x' field. result.y = 0.1 // Assign value to the 'y' field. result.z = 1.0 // Assign value to the 'z' field. return result // Return the fully initialized struct. } // The `getWhite` function allows direct assignment to the tuple (r, g, b) without additional storage. let (r, g, b) = getWhite() @assert(r == 0.5) // Verifies the 'x' field is correctly assigned. @assert(g == 0.1) // Verifies the 'y' field is correctly assigned. @assert(b == 1.0) // Verifies the 'z' field is correctly assigned. }

Returning arrays efficiently

The use of retval is highly recommended when returning large data structures like arrays or structs. By leveraging retval, you can avoid unnecessary memory operations, such as clearing or copying large objects, resulting in more efficient and performant code. This approach is especially useful in performance-critical applications where minimizing overhead is crucial.

#test { func toto() -> [255] s32 { // Using `retval = undefined` avoids clearing the array, improving performance. var result: retval = undefined // 'retval' here is an array of 255 integers. for i in 255: // Loop through each index in the array. result[i] = i // Assign the index value to each array element. return result // Return the fully populated array. } var arr = toto() @assert(arr[0] == 0) // Verifies that the first element is correctly set. @assert(arr[100] == 100) // Verifies that the 101st element is correctly set. @assert(arr[254] == 254) // Verifies that the last element is correctly set. }

Foreign

Interoperability with External Modules

Swag provides the ability to interoperate with external modules, such as Dynamic Link Libraries (DLLs) on Windows, which export C functions. This interoperability allows Swag code to invoke functions from these external libraries, enabling seamless integration with system-level APIs and third-party libraries. This feature is particularly powerful for extending Swag applications with capabilities provided by external systems.

Declaring External Functions

To use functions from external modules, you declare them in your Swag code with the Swag.Foreign attribute. This attribute specifies the external module where the function is located. The module name can refer to either a Swag-compiled module or an external system module, depending on your needs. The exact location of these external modules varies by operating system, so it is important to specify the correct module name based on the target platform.

#[Swag.Foreign("kernel32")] func ExitProcess(uExitCode: u32); // Declares the 'ExitProcess' function from 'kernel32.dll' #[Swag.Foreign("kernel32")] { func Sleep(dwMilliseconds: u32); // Declares the 'Sleep' function from 'kernel32.dll' }

Example: Windows API

In this example, two functions (ExitProcess and Sleep) are declared as external functions that reside in the kernel32.dll module, a core system library on Windows. By declaring these functions with Swag.Foreign, you can directly invoke Windows API functions within your Swag code, allowing for system-level operations such as exiting a process or pausing execution for a specified amount of time.

Linking to External Libraries

When working with external modules, it is essential to ensure that the corresponding library is linked to the final executable. This linking process is crucial because it allows the linker to resolve the references to the external functions declared in your code.

  • Use the #foreignlib directive to specify which external library should be linked during the compilation process.
  • This directive informs the Swag compiler to include the specified external module during the linking stage, ensuring

that all external function calls are correctly resolved at runtime.

// Links the 'kernel32.dll' library to the executable, resolving external function calls. #foreignlib "kernel32"

Special functions

#global skip

Main Function (#main)

The #main function is the primary entry point for the program, similar to the main() function in languages like C or C++. This function is unique within each module, meaning you can only define it once per module.

In the context of an executable program, #main is where the program's execution begins. Any code placed within this function will be the first to execute when the program runs.

#main { }

Handling Program Arguments

Unlike the main() function in C, the #main function in this language does not take arguments directly. Instead, command-line arguments can be retrieved using the intrinsic @args, which provides a slice containing all the arguments passed to the program.

Here’s an example demonstrating how to work with command-line arguments:

#main { // Retrieve the program arguments using the @args intrinsic var myArgs = @args() // Determine the number of arguments passed var count = @countof(myArgs) // Handle a specific argument, for example, enabling fullscreen mode if myArgs[0] == "fullscreen" { // Logic to initiate fullscreen mode would go here ... } }

Pre-Main Function (#premain)

The #premain function is executed after all #init functions across all modules have completed, but before the #main function begins.

This function is typically used for tasks that need to be performed after module initialization, yet before the main program logic is executed. It's useful for setup tasks that depend on the initial state of the program being fully established.

#premain { }

Initialization Function (#init)

The #init function is executed at runtime during the module initialization phase. You can define multiple #init functions within the same module, allowing different parts of the module to initialize independently.

The execution order of #init functions within the same module is undefined, so you should not rely on a specific sequence of initialization tasks. However, all #init functions will execute before any code in the #main or #premain functions.

#init { }

Drop Function (#drop)

The #drop function acts as a cleanup function and is called when a module is unloaded at runtime. It serves as the counterpart to #init, ensuring that any resources allocated during initialization are properly released.

Just like #init, you can define multiple #drop functions within a module, and the order of their execution is undefined. However, #drop functions are guaranteed to run in the reverse order of their corresponding #init functions, ensuring a logical cleanup process.

#drop { }

Test Function (#test)

The #test function is a specialized function designed for testing purposes. It is typically used within the tests/ folder of your workspace and is executed only when the program is run in test mode.

This function is crucial for validating the correctness and functionality of your code in a controlled environment before it is deployed or released. It allows you to define test cases and assertions to ensure that your code behaves as expected.

#test { }

Intrinsics

Intrinsics in Swag

Intrinsics are built-in functions provided by the Swag compiler that offer low-level operations, often directly mapping to specific machine instructions or providing essential compiler utilities. All intrinsics in Swag are prefixed with @, which is reserved exclusively for these functions.

This document provides a categorized list of all intrinsics available in Swag.

#global skip

Base Intrinsics

These base intrinsics provide fundamental functionalities that are commonly needed across various Swag programs.

func @assert(value: bool); // Asserts that a condition is true, used for debugging. func @breakpoint(); // Triggers a breakpoint in the debugger. func @getcontext() -> *Swag.Context; // Retrieves the current execution context. func @setcontext(context: const *Swag.Context); // Sets the current execution context. func @isbytecode() -> bool; // Checks if the code is being executed as bytecode. func @compiler() -> Swag.ICompiler; // Retrieves the current compiler interface. func @args() -> const [..] string; // Returns the command-line arguments passed to the program. @panic() // Triggers a panic, stopping program execution. @compilererror() // Generates a compile-time error. @compilerwarning() // Generates a compile-time warning.

Built-in Intrinsics

These intrinsics provide essential built-in operations related to type and memory management, typically for low-level or performance-critical code.

@spread() // Expands a value into a wider context. @init() // Initializes a variable or memory area. @drop() // Destroys a variable or memory area. @postmove() // Called after a move operation to handle post-move logic. @postcopy() // Called after a copy operation to handle post-copy logic. #sizeof() // Returns the size, in bytes, of a type or variable. #alignof() // Returns the alignment requirement of a type. #offsetof() // Returns the offset, in bytes, of a field within a struct. #typeof() // Returns the type of a given expression. @kindof() // Returns the kind (e.g., primitive, struct) of a type. @countof() // Returns the number of elements in an array. #stringof() // Returns the string representation of a type or expression. @dataof() // Returns a pointer to the underlying data of a type. @mkslice() // Creates a slice from a given data pointer and length. @mkstring() // Creates a string from a given data pointer and length. @mkany() // Creates a generic `any` type from a given value. @mkinterface() // Creates an interface type from a given implementation. @mkcallback() // Creates a callback from a given function pointer. @pinfos() // Retrieves program information. #isconstexpr() // Checks if an expression is a constant expression. @itftableof() // Returns the interface table for a given type.

Memory-related Intrinsics

These intrinsics offer memory management operations, allowing for fine-grained control over memory allocation, deallocation, and manipulation.

func @alloc(size: u64) -> *void; // Allocates a block of memory of the given size. func @realloc(ptr: *void, size: u64) -> *void; // Reallocates a block of memory to a new size. func @free(ptr: *void); // Frees a previously allocated block of memory. func @memset(dst: *void, value: u8, size: u64); // Sets a block of memory to a specific value. func @memcpy(dst: *void, src: const *void, size: u64); // Copies a block of memory from one location to another. func @memmove(dst: *void, src: const *void, size: u64); // Moves a block of memory, handling overlapping areas. func @memcmp(dst, src: const *void, size: u64) -> s32; // Compares two blocks of memory. func @strlen(value: const *u8) -> u64; // Returns the length of a null-terminated string.

Atomic Operations

Atomic operations provide thread-safe manipulation of variables in shared memory, ensuring data consistency without the need for explicit locking mechanisms.

func @atomadd(addr: *s8, value: s8) -> s8; func @atomadd(addr: *s16, value: s16) -> s16; func @atomadd(addr: *s32, value: s32) -> s32; func @atomadd(addr: *s64, value: s64) -> s64; func @atomadd(addr: *u8, value: u8) -> u8; func @atomadd(addr: *u16, value: u16) -> u16; func @atomadd(addr: *u32, value: u32) -> u32; func @atomadd(addr: *u64, value: u64) -> u64; func @atomand(addr: *s8, value: s8) -> s8; func @atomand(addr: *s16, value: s16) -> s16; func @atomand(addr: *s32, value: s32) -> s32; func @atomand(addr: *s64, value: s64) -> s64; func @atomand(addr: *u8, value: u8) -> u8; func @atomand(addr: *u16, value: u16) -> u16; func @atomand(addr: *u32, value: u32) -> u32; func @atomand(addr: *u64, value: u64) -> u64; func @atomor(addr: *s8, value: s8) -> s8; func @atomor(addr: *s16, value: s16) -> s16; func @atomor(addr: *s32, value: s32) -> s32; func @atomor(addr: *s64, value: s64) -> s64; func @atomor(addr: *u8, value: u8) -> u8; func @atomor(addr: *u16, value: u16) -> u16; func @atomor(addr: *u32, value: u32) -> u32; func @atomor(addr: *u64, value: u64) -> u64; func @atomxor(addr: *s8, value: s8) -> s8; func @atomxor(addr: *s16, value: s16) -> s16; func @atomxor(addr: *s32, value: s32) -> s32; func @atomxor(addr: *s64, value: s64) -> s64; func @atomxor(addr: *u8, value: u8) -> u8; func @atomxor(addr: *u16, value: u16) -> u16; func @atomxor(addr: *u32, value: u32) -> u32; func @atomxor(addr: *u64, value: u64) -> u64; func @atomxchg(addr: *s8, exchangeWith: s8) -> s8; func @atomxchg(addr: *s16, exchangeWith: s16) -> s16; func @atomxchg(addr: *s32, exchangeWith: s32) -> s32; func @atomxchg(addr: *s64, exchangeWith: s64) -> s64; func @atomxchg(addr: *u8, exchangeWith: u8) -> u8; func @atomxchg(addr: *u16, exchangeWith: u16) -> u16; func @atomxchg(addr: *u32, exchangeWith: u32) -> u32; func @atomxchg(addr: *u64, exchangeWith: u64) -> u64; func @atomcmpxchg(addr: *s8, compareTo, exchangeWith: s8) -> s8; func @atomcmpxchg(addr: *s16, compareTo, exchangeWith: s16) -> s16; func @atomcmpxchg(addr: *s32, compareTo, exchangeWith: s32) -> s32; func @atomcmpxchg(addr: *s64, compareTo, exchangeWith: s64) -> s64; func @atomcmpxchg(addr: *u8, compareTo, exchangeWith: u8) -> u8; func @atomcmpxchg(addr: *u16, compareTo, exchangeWith: u16) -> u16; func @atomcmpxchg(addr: *u32, compareTo, exchangeWith: u32) -> u32; func @atomcmpxchg(addr: *u64, compareTo, exchangeWith: u64) -> u64;

Math Intrinsics

These intrinsics provide various mathematical operations, including trigonometric, logarithmic, and other common functions, offering precise control over mathematical calculations in Swag programs.

func @sqrt(value: f32) -> f32; // Computes the square root of a 32-bit floating-point number. func @sqrt(value: f64) -> f64; // Computes the square root of a 64-bit floating-point number. func @sin(value: f32) -> f32; // Computes the sine of a 32-bit floating-point number. func @sin(value: f64) -> f64; // Computes the sine of a 64-bit floating-point number. func @cos(value: f32) -> f32; // Computes the cosine of a 32-bit floating-point number. func @cos(value: f64) -> f64; // Computes the cosine of a 64-bit floating-point number. func @tan(value: f32) -> f32; // Computes the tangent of a 32-bit floating-point number. func @tan(value: f64) -> f64; // Computes the tangent of a 64-bit floating-point number. func @sinh(value: f32) -> f32; // Computes the hyperbolic sine of a 32-bit floating-point number. func @sinh(value: f64) -> f64; // Computes the hyperbolic sine of a 64-bit floating-point number. func @cosh(value: f32) -> f32; // Computes the hyperbolic cosine of a 32-bit floating-point number. func @cosh(value: f64) -> f64; // Computes the hyperbolic cosine of a 64-bit floating-point number. func @tanh(value: f32) -> f32; // Computes the hyperbolic tangent of a 32-bit floating-point number. func @tanh(value: f64) -> f64; // Computes the hyperbolic tangent of a 64-bit floating-point number. func @asin(value: f32) -> f32; // Computes the arc sine of a 32-bit floating-point number. func @asin(value: f64) -> f64; // Computes the arc sine of a 64-bit floating-point number. func @acos(value: f32) -> f32; // Computes the arc cosine of a 32-bit floating-point number. func @acos(value: f64) -> f64; // Computes the arc cosine of a 64-bit floating-point number. func @atan(value: f32) -> f32; // Computes the arc tangent of a 32-bit floating-point number. func @atan(value: f64) -> f64; // Computes the arc tangent of a 64-bit floating-point number. func @log(value: f32) -> f32; // Computes the natural logarithm of a 32-bit floating-point number. func @log(value: f64) -> f64; // Computes the natural logarithm of a 64-bit floating-point number. func @log2(value: f32) -> f32; // Computes the base-2 logarithm of a 32-bit floating-point number. func @log2(value: f64) -> f64; // Computes the base-2 logarithm of a 64-bit floating-point number. func @log10(value: f32) -> f32; // Computes the base-10 logarithm of a 32-bit floating-point number. func @log10(value: f64) -> f64; // Computes the base-10 logarithm of a 64-bit floating-point number. func @floor(value: f32) -> f32; // Computes the floor of a 32-bit floating-point number. func @floor(value: f64) -> f64; // Computes the floor of a 64-bit floating-point number. func @ceil(value: f32) -> f32; // Computes the ceiling of a 32-bit floating-point number. func @ceil(value: f64) -> f64; // Computes the ceiling of a 64-bit floating-point number. func @trunc(value: f32) -> f32; // Truncates a 32-bit floating-point number to its integer part. func @trunc(value: f64) -> f64; // Truncates a 64-bit floating-point number to its integer part. func @round(value: f32) -> f32; // Rounds a 32-bit floating-point number to the nearest integer. func @round(value: f64) -> f64; // Rounds a 64-bit floating-point number to the nearest integer. func @abs(value: s8) -> s8; // Computes the absolute value of an 8-bit signed integer. func @abs(value: s16) -> s16; // Computes the absolute value of a 16-bit signed integer. func @abs(value: s32) -> s32; // Computes the absolute value of a 32-bit signed integer. func @abs(value: s64) -> s64; // Computes the absolute value of a 64-bit signed integer. func @abs(value: f32) -> f32; // Computes the absolute value of a 32-bit floating-point number. func @abs(value: f64) -> f64; // Computes the absolute value of a 64-bit floating-point number. func @exp(value: f32) -> f32; // Computes the exponential function of a 32-bit floating-point number. func @exp(value: f64) -> f64; // Computes the exponential function of a 64-bit floating-point number. func @exp2(value: f32) -> f32; // Computes 2 raised to the power of a 32-bit floating-point number. func @exp2(value: f64) -> f64; // Computes 2 raised to the power of a 64-bit floating-point number. func @pow(value1, value2: f32) -> f32; // Computes the power function for 32-bit floating-point numbers. func @pow(value1, value2: f64) -> f64; // Computes the power function for 64-bit floating-point numbers. func @min(value1, value2: s8) -> s8; // Returns the minimum of two 8-bit signed integers. func @min(value1, value2: s16) -> s16; // Returns the minimum of two 16-bit signed integers. func @min(value1, value2: s32) -> s32; // Returns the minimum of two 32-bit signed integers. func @min(value1, value2: s64) -> s64; // Returns the minimum of two 64-bit signed integers. func @min(value1, value2: u8) -> u8; // Returns the minimum of two 8-bit unsigned integers. func @min(value1, value2: u16) -> u16; // Returns the minimum of two 16-bit unsigned integers. func @min(value1, value2: u32) -> u32; // Returns the minimum of two 32-bit unsigned integers. func @min(value1, value2: u64) -> u64; // Returns the minimum of two 64-bit unsigned integers. func @min(value1, value2: f32) -> f32; // Returns the minimum of two 32-bit floating-point numbers. func @min(value1, value2: f64) -> f64; // Returns the minimum of two 64-bit floating-point numbers. func @max(value1, value2: s8) -> s8; // Returns the maximum of two 8-bit signed integers. func @max(value1, value2: s16) -> s16; // Returns the maximum of two 16-bit signed integers. func @max(value1, value2: s32) -> s32; // Returns the maximum of two 32-bit signed integers. func @max(value1, value2: s64) -> s64; // Returns the maximum of two 64-bit signed integers. func @max(value1, value2: u8) -> u8; // Returns the maximum of two 8-bit unsigned integers. func @max(value1, value2: u16) -> u16; // Returns the maximum of two 16-bit unsigned integers. func @max(value1, value2: u32) -> u32; // Returns the maximum of two 32-bit unsigned integers. func @max(value1, value2: u64) -> u64; // Returns the maximum of two 64-bit unsigned integers. func @max(value1, value2: f32) -> f32; // Returns the maximum of two 32-bit floating-point numbers. func @max(value1, value2: f64) -> f64; // Returns the maximum of two 64-bit floating-point numbers. func @bitcountnz(value: u8) -> u8; // Counts the number of set bits in an 8-bit unsigned integer. func @bitcountnz(value: u16) -> u16; // Counts the number of set bits in a 16-bit unsigned integer. func @bitcountnz(value: u32) -> u32; // Counts the number of set bits in a 32-bit unsigned integer. func @bitcountnz(value: u64) -> u64; // Counts the number of set bits in a 64-bit unsigned integer. func @bitcounttz(value: u8) -> u8; // Counts the number of trailing zeros in an 8-bit unsigned integer. func @bitcounttz(value: u16) -> u16; // Counts the number of trailing zeros in a 16-bit unsigned integer. func @bitcounttz(value: u32) -> u32; // Counts the number of trailing zeros in a 32-bit unsigned integer. func @bitcounttz(value: u64) -> u64; // Counts the number of trailing zeros in a 64-bit unsigned integer. func @bitcountlz(value: u8) -> u8; // Counts the number of leading zeros in an 8-bit unsigned integer. func @bitcountlz(value: u16) -> u16; // Counts the number of leading zeros in a 16-bit unsigned integer. func @bitcountlz(value: u32) -> u32; // Counts the number of leading zeros in a 32-bit unsigned integer. func @bitcountlz(value: u64) -> u64; // Counts the number of leading zeros in a 64-bit unsigned integer. func @byteswap(value: u16) -> u16; // Swaps the byte order of a 16-bit unsigned integer. func @byteswap(value: u32) -> u32; // Swaps the byte order of a 32-bit unsigned integer. func @byteswap(value: u64) -> u64; // Swaps the byte order of a 64-bit unsigned integer. func @rol(value, num: u8) -> u8; // Rotates an 8-bit unsigned integer left by a specified number of bits. func @rol(value, num: u16) -> u16; // Rotates a 16-bit unsigned integer left by a specified number of bits. func @rol(value, num: u32) -> u32; // Rotates a 32-bit unsigned integer left by a specified number of bits. func @rol(value, num: u64) -> u64; // Rotates a 64-bit unsigned integer left by a specified number of bits. func @ror(value, num: u8) -> u8; // Rotates an 8-bit unsigned integer right by a specified number of bits. func @ror(value, num: u16) -> u16; // Rotates a 16-bit unsigned integer right by a specified number of bits. func @ror(value, num: u32) -> u32; // Rotates a 32-bit unsigned integer right by a specified number of bits. func @ror(value, num: u64) -> u64; // Rotates a 64-bit unsigned integer right by a specified number of bits. func @muladd(val1, val2, val3: f32) -> f32; // Performs a multiply-add operation for 32-bit floating-point numbers. func @muladd(val1, val2, val3: f64) -> f64; // Performs a multiply-add operation for 64-bit floating-point numbers.

Init

@init Intrinsic

The @init intrinsic in Swag is used to reinitialize a variable or a memory block to its default value. This intrinsic is especially useful when you need to reset the state of variables or memory blocks without manually setting each field or element. The ability to reset variables to their default states or to a specific value simplifies state management in Swag applications.

Reinitializing a Single Variable

To reinitialize a single variable to its default value, simply pass the variable as an argument to @init.

#test { var x = 666 @init(x) // Reinitialize 'x' to its default value @assert(x == 0) // The default value for a simple variable like 'x' is 0 }

Reinitializing Multiple Elements

You can also specify a pointer to a memory block and the count of elements to reinitialize a specific number of elements within that memory block. This is useful for resetting arrays or parts of them.

#test { var x = [1, 2] @init(&x, 2) // Reinitialize the first 2 elements of the array 'x' @assert(x[0] == 0) // Both elements are reset to 0 @assert(x[1] == 0) x[0] = 1 x[1] = 2 @init(x) // You can also reinitialize the entire variable directly @assert(x[0] == 0) @assert(x[1] == 0) }

Initializing with a Specific Value

The @init intrinsic can also initialize a variable with a specific value instead of its default. This provides a flexible way to reset variables to any desired state.

#test { var x = 666'f32 @init(x)(3.14) // Reinitialize 'x' to 3.14 instead of 0 @assert(x == 3.14) }

Initializing Arrays with a Specific Value

The @init intrinsic can be applied to arrays to reinitialize all elements with a specific value, ensuring consistency across the array.

#test { var x = [1, 2] @init(&x, 2)(555) // Reinitialize both elements of the array to 555 @assert(x[0] == 555) @assert(x[1] == 555) }

Reinitializing Structs

When applied to structs, @init restores the struct to its default state as defined in its declaration. This is particularly useful for resetting complex data structures.

#test { struct RGB { r = 1, g = 2, b = 3 } var rgb: RGB{10, 20, 30} @assert(rgb.r == 10) @assert(rgb.g == 20) @assert(rgb.b == 30) @init(rgb) // Reset 'rgb' to its default values @assert(rgb.r == 1) @assert(rgb.g == 2) @assert(rgb.b == 3) }

Specifying Initialization Values for Structs

You can also initialize a struct with specific values directly using @init, providing a convenient way to set all fields at once.

#test { struct RGB { r = 1, g = 2, b = 3 } var rgb: RGB{10, 20, 30} @assert(rgb.r == 10) @assert(rgb.g == 20) @assert(rgb.b == 30) @init(rgb)(5, 6, 7) // Initialize 'rgb' with specific values @assert(rgb.r == 5) @assert(rgb.g == 6) @assert(rgb.b == 7) }

Reinitializing Arrays of Structs

The @init intrinsic can be used with arrays of structs, allowing you to reinitialize each element with specific values. This is particularly useful for resetting large data structures efficiently.

#test { struct RGB { r = 1, g = 2, b = 3 } var rgb: [4] RGB @init(&rgb, 4)(5, 6, 7) // Initialize all elements of the array to (5, 6, 7) @assert(rgb[3].r == 5) @assert(rgb[3].g == 6) @assert(rgb[3].b == 7) @init(rgb)(50, 60, 70) // Reinitialize the array with new values @assert(rgb[3].r == 50) @assert(rgb[3].g == 60) @assert(rgb[3].b == 70) }

Drop

@drop Intrinsic

The @drop intrinsic calls the opDrop method if it is defined for the struct. This ensures that any necessary cleanup operations (such as freeing resources) are performed before the variable is reinitialized. @drop is particularly useful in resource management, where explicit cleanup is required before resetting the variable.

#test { struct RGB { r = 1, g = 2, b = 3 } var rgb: [4] RGB // Calling `@drop` on the array. If `RGB` had an `opDrop` defined, it would be called here. @drop(&rgb, 4) @init(&rgb, 4)(5, 6, 7) // Reinitialize after dropping @assert(rgb[3].r == 5) @assert(rgb[3].g == 6) @assert(rgb[3].b == 7) }

Generics

Functions

Generic Functions

A function can be made generic by specifying type parameters after the func keyword. These type parameters allow the function to operate on various types using the same implementation. The generic type parameters are placed within parentheses after func. When calling the function, the generic types are specified using funcCall'(type1, type2, ...). If there is only one generic parameter, you can omit the parentheses.

#test { { // Example of a generic function where 'T' is the generic type. func(var T) myFunc(val: T) => 2 * val @assert(myFunc's32(2) == 4) // Explicitly passing 's32' as the generic type. @assert(myFunc'f32(2.0) == 4.0) // Explicitly passing 'f32' as the generic type. } { // Declaring the generic type without 'var'. func(T) myFunc(val: T) => 2 * val @assert(myFunc's32(2) == 4) // Type 's32' is inferred as the generic type. @assert(myFunc'f32(2.0) == 4.0) // Type 'f32' is inferred as the generic type. } { // Setting a default value for the generic type. func(T = s32) myFunc(val: T) => 2 * val @assert(myFunc(2's32) == 4) // Uses default type 's32'. @assert(myFunc'f32(2.0) == 4.0) // Overrides the default type with 'f32'. } { // Using multiple generic parameters. func(K, V) myFunc(key: K, value: V) => value @assert(myFunc(2's32, "value") == "value") // Both K and V are deduced from the parameters. @assert(myFunc'(s32, string)(2, "value") == "value") // K and V are specified explicitly. @assert(myFunc(2's32, true) == true) // Type deduction used for both K and V. @assert(myFunc'(s32, bool)(2, true) == true) // Explicit type declaration for K and V. } }

Type Deduction

Generic types can often be deduced from the function's parameters, eliminating the need to specify the type explicitly at the call site.

#test { func(T) myFunc(val: T) => 2 * val @assert(myFunc(2's32) == 4) // The type 'T' is deduced as 's32'. @assert(myFunc(2.0'f32) == 4.0) // The type 'T' is deduced as 'f32'. }

Using Constants as Generic Parameters

In addition to types, you can also specify constants as generic parameters.

In the example below, N is a constant of type s32.

#test { func(const N: s32) myFunc() = @assert(N == 10) myFunc'10() // Call the function with a constant value of 10. }

const can be omitted when declaring constants, as an identifier followed by a type is considered a constant.

#test { func(N: s32) myFunc() = @assert(N == 10) myFunc'10() // Function called with a constant value of 10. }

You can also assign a default value to a constant parameter.

#test { func(N: s32 = 10) myFunc() = @assert(N == 10) myFunc() // Calls the function using the default value of 10. }

If you declare the constant using const, the type can be omitted, and it will be deduced from the assignment expression.

#test { func(const N = 10) myFunc() = @assert(N == 10) myFunc() // Constant value is deduced as 's32'. }

Mixing Types and Constants

You can mix types and constants in generic parameters.

#test { { // Example where `T` is a type and `N` is a constant of type `s32`. func(T, N: s32) myFunc(x: T) => x * N alias call = myFunc'(s32, 10) @assert(call(2) == 20) // Function call with 's32' type and 10 constant. @assert(call(100) == 1000) // Another call with the same type and constant. } { // Declaring multiple constants requires specifying the type for each. func(T: s32, N: s32) myFunc() => T * N @assert(myFunc'(5, 10)() == 50) // Function call with two 's32' constants. } { // Declaring multiple types with default values. func(T = s32, V = s32) myFunc(x: T, y: V) => x * y @assert(myFunc(1's32, 2'f32) == 2.0) // Calls with 's32' and 'f32', type deduced. @assert(myFunc(1's32, 2's32) == 2) // Calls with 's32' for both parameters. } }

Structs

Generic Structs

Structs in Swag can also be made generic, allowing them to operate with different types and constants.

#test { { // Example of a generic struct where `T` is the generic type. struct(T) Struct { val: T } var x: Struct's32 @assert(#typeof(x.val) == s32) // The type of 'val' is deduced as 's32'. var x1: Struct'f32 @assert(#typeof(x1.val) == f32) // The type of 'val' is deduced as 'f32'. } { // A more complex example with both a type and a constant as generic parameters. struct(T, N: s32) Struct { val: [N] T // An array of 'N' elements of type 'T'. } var x: Struct'(bool, 10) @assert(#typeof(x.val) == #type [10] bool) // The type is an array of 10 booleans. } }

Where constraints

Single Evaluation

The where clause in Swag is a powerful tool for applying constraints on function invocations, ensuring that they can only be called when specific conditions are met. This feature is particularly useful in generic functions where you want to restrict the permissible types or values passed as arguments.

When the where expression evaluates to false, the function will not be considered during the call. If no alternative overloads match, the compiler will raise an error. Notably, the where expression is evaluated only once, usually during the function's instantiation. This makes it ideal for applying constraints on generic parameters in a consistent and predictable manner.

#test { // This function validates the type and executes only if the generic type is `s32` or `s64`. func(T) sum(x: T...)->T where T == s32 or T == s64 // Restricting `T` to `s32` or `s64` { var total = 0'T foreach it in x: total += it // Accumulate the values return total // Return the sum } // These calls are valid since `T` is `s32` or `s64`. let res1 = sum's32(1, 2) // Sum with `s32` @assert(res1 == 3) // Assert that result is 3 let res2 = sum's64(10, 20) // Sum with `s64` @assert(res2 == 30) // Assert that result is 30 // The following would generate an error since `f32` is not a matching type. // var res3 = sum'f32(1, 2) }

Generic Specialization

The where clause also facilitates the creation of specialized versions of generic functions. This feature allows you to provide distinct implementations based on the type or value of the parameters, enhancing flexibility and efficiency.

#test { // Specialized implementation for `s32`. #[Swag.Overload] func(T) isNull(x: T)->bool // Specializing for `s32` where T == s32 { return x == 0 // Return true if `x` is zero } // Specialized implementation for `f32` and `f64`. #[Swag.Overload] func(T) isNull(x: T)->bool // Specializing for `f32` or `f64` where T == f32 or T == f64 { return @abs(x) < 0.01 // Return true if `x` is close to zero } @assert(isNull(0's32)) // Assert true for `s32` zero @assert(isNull(0.001'f32)) // Assert true for `f32` close to zero }

Block-based where Clause

The where clause can also take the form of a block returning a bool value. This allows for more complex conditional logic that may involve multiple checks, making it suitable for advanced validation scenarios.

#test { func(T) sum(x: T...)->T // Function with a block-based `where` where { if #typeof(T) == s32 or #typeof(T) == s64: // Check if `T` is `s32` or `s64` return true // Return true if valid return false // Return false otherwise } { var total = 0'T // Initialize total with type `T` foreach it in x: total += it // Accumulate the values return total // Return the sum } }

Custom Compile-time Errors

Using the @compilererror intrinsic, you can trigger custom compile-time errors when the where condition is not met. This provides a mechanism for generating clear and specific error messages, guiding users when a function is used incorrectly.

#test { func(T) sum(x, y: T)->T // Function with custom compile-time error where { if T == s32 or T == s64: // Check if `T` is `s32` or `s64` return true // Return true if valid @compilererror("Invalid type " ++ #stringof(T), #location(T)) return false // Return false after error } { return x + y // Return the sum of `x` and `y` } // This will trigger a compile-time error as `f32` is not a valid type. // var x = sum'f32(1, 2) }

Generic Structs with where

The where clause can also be applied to generic structs. If the condition is not met, an error will be generated immediately since there is no overload resolution for structs, providing a direct mechanism for enforcing constraints on struct instantiation.

#test { struct(T) Point // Struct with type constraints where T == f32 or T == f64 { x, y: T // Coordinates with type `T` } // Valid instantiation with `f32`. var v: Point'f32 // Create a Point with `f32` // The following would generate an error since `s32` is not allowed. // var v: Point's32 }

Multiple Evaluations

By utilizing the where<call> mode, the where clause is evaluated for each function call, rather than just once per function instantiation. This is particularly useful for conditions that depend on the actual arguments passed to the function, as long as these arguments can be evaluated at compile time.

#test { { // Function to ensure that `y` is not zero at compile time. func div(x, y: s32)->s32 // Division function with compile-time check where<call> { // Verify if `y` is a compile-time constant. if !#isconstexpr(y): return true // Allow if `y` is not constant if y == 0: @compilererror("Division by zero", #location(y)) return true // Allow otherwise } { return x / y } // Valid division. var x1 = div(1, 1) // Division with non-zero `y` // The following would generate a compile-time error due to division by zero. // var x2 = div(1, 0) } { // Function with different implementations based on whether `x` is known at compile time. #[Swag.Overload] func first(x: s32)->s32 // Overload for constexpr `x` where<call> #isconstexpr(x) { return 555 // Return 555 if `x` is constexpr } // Overload for the case where `x` is not known at compile time. #[Swag.Overload] func first(x: s32)->s32 // Overload for non-constexpr `x` where<call> !#isconstexpr(x) { return 666 // Return 666 if `x` is not constexpr } // Will call the first version because `x` is a literal. @assert(first(0) == 555) // Will call the second version because `x` is a variable. var a: s32 @assert(first(a) == 666) } }

Attributes

Attributes are tags associated with functions, structures etc...

User attributes

User Attributes in Swag

Attributes in Swag serve as a powerful mechanism for annotating various elements of the code, such as functions and structs, with metadata. These annotations, defined using the attr keyword, can be leveraged for a variety of purposes, including code generation, documentation enhancement, and runtime reflection. By attaching attributes, developers can enrich their code with additional information that can be accessed both at compile-time and runtime.

using Swag attr AttributeA() // A simple attribute without parameters

Attributes with Parameters

Attributes in Swag can accept parameters in a manner similar to functions. These parameters enable the customization of the attribute's behavior or configuration when it is applied to a code element.

attr AttributeB(x, y: s32, z: string) // Attribute with multiple parameters

Attributes with Default Values

Swag attributes support parameters with default values, providing flexibility when applying attributes. This means that when such attributes are used, some or all of the parameters can be omitted, and the default values will be applied automatically.

attr AttributeBA(x: s32, y: string = "string") // Attribute with a default parameter

Restricting Attribute Usage

Swag allows developers to control where an attribute can be applied through the AttrUsage specifier. By restricting an attribute's usage to specific elements (such as functions or structs), you ensure that the attribute is only used in appropriate contexts, thereby enhancing code safety and clarity.

#[AttrUsage(AttributeUsage.Function)] attr AttributeC() // Attribute restricted to function usage

Applying Attributes

To apply attributes in Swag, use the syntax #[attribute, attribute...] immediately before the element you wish to annotate. This syntax supports the application of multiple attributes to a single element by listing them sequentially, separated by commas.

#[AttributeA, AttributeB(0, 0, "string")] func function1() {} // Function annotated with multiple attributes

Multiple Usages

An attribute in Swag can be designed to be applicable to multiple types of code elements by specifying a combination of AttrUsage values using a bitwise OR operation. This capability allows a single attribute to be reused across different contexts, such as both functions and structs.

#[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct)] attr AttributeD(x: s32) // Attribute applicable to both functions and structs #[AttributeD(6)] func function2() {} // Function annotated with a multi-usage attribute #[AttributeD(150)] struct struct1 {} // Struct annotated with the same attribute

Retrieving Attributes at Runtime

Swag supports the retrieval and inspection of attributes at runtime through type reflection. This feature enables dynamic interaction with the metadata associated with various code elements, allowing developers to adapt behavior based on the presence or configuration of attributes.

#test { let type = #typeof(function2) // Retrieve the type of the function @assert(@countof(type.attributes) == 1) // Assert that the function has exactly one attribute }

Predefined attributes

This is the list of predefined attributes. All are located in the reserved Swag namespace.

#global skip // Can be executed at compile time #[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct)] attr ConstExpr() // On a function or a struct, this will print the associated generated bytecode // (right after generation, without bytecode optimizations) #[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct | AttributeUsage.File)] attr PrintGenBc() // On a function or a struct, this will print the associated generated bytecode (after bytecode optimizations) #[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct | AttributeUsage.File)] attr PrintBc() // The following function or variable is only defined at compile time #[AttrUsage(AttributeUsage.Function | AttributeUsage.GlobalVariable | AttributeUsage.Constant)] attr Compiler() // Force a function to be inlined #[AttrUsage(AttributeUsage.Function)] attr Inline() // Never inline the following function. // This is a hint for the 'llvm' backend. #[AttrUsage(AttributeUsage.Function)] attr NoInline() // The following function is a 'macro' #[AttrUsage(AttributeUsage.Function)] attr Macro() // The following function is a 'mixin' #[AttrUsage(AttributeUsage.Function)] attr Mixin() // Can force an 'opCast' special function to work as implicit #[AttrUsage(AttributeUsage.Function)] attr Implicit() // The following switch must be complete #[AttrUsage(AttributeUsage.Function)] attr Complete() // The following function can be overloaded #[AttrUsage(AttributeUsage.Function)] attr Overload() // A 'return' in the following inlined function must be done in the callee context #[AttrUsage(AttributeUsage.Function)] attr CalleeReturn() // The following function is foreign (imported) #[AttrUsage(AttributeUsage.Function)] attr Foreign(module: string, function: string = "") // The following function accepts that the called does not use its return value #[AttrUsage(AttributeUsage.Function | AttributeUsage.Variable)] attr Discardable() // The following definition is deprecated and should not be used #[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct | AttributeUsage.Enum | AttributeUsage.EnumValue)] attr Deprecated(msg: string = null) // The following function is forced to not be generic, even if defined inside a generic 'struct'. #[AttrUsage(AttributeUsage.Function)] attr NotGeneric() // Put the following global variable in the 'tls' segment. // A copy of the variable will be available for each thread. #[AttrUsage(AttributeUsage.GlobalVariable)] attr Tls() // 'struct' packing information #[AttrUsage(AttributeUsage.Struct)] attr Pack(value: u8) // The following struct should never be copied #[AttrUsage(AttributeUsage.Struct)] attr NoCopy() // When exporting the following struct,: not export its content #[AttrUsage(AttributeUsage.Struct)] attr Opaque() // Struct field member relocation. // The field offset in the struct should be the same as the variable 'name' #[AttrUsage(AttributeUsage.StructVariable)] attr Offset(name: string) // The following enum is a set of flags #[AttrUsage(AttributeUsage.Enum)] attr EnumFlags() // The following enum can be used to index arrays without casting #[AttrUsage(AttributeUsage.Enum)] attr EnumIndex() // The following enum can't have duplicated values #[AttrUsage(AttributeUsage.Enum)] attr NoDuplicate() // The following switch is incomplete #[AttrUsage(AttributeUsage.Enum)] attr Incomplete() #[AttrUsage(AttributeUsage.Struct)] attr ExportType(what: string) // Do not generate documentation. #[AttrUsage(AttributeUsage.All | AttributeUsage.File)] attr NoDoc() // Enable/Disable one or more safety checks. // For example: // ```swag // #[Swag.Safety("", false)] // Disable all // #[Swag.Safety("boundcheck|nan", false)] // Disable 'boundcheck' and 'nan' checks // ``` // Safety checks are: // | 'boundcheck' | Check out of bound access // | 'overflow' | Check type conversion lost of bits or precision // | 'math' | Various math checks (like a negative '@sqrt') // | 'switch' | Check an invalid case in a '#[Swag.Complete]' switch // | 'unreachable' | Panic if an '@unreachable' instruction is executed // | 'any' | Panic if a cast from a 'any' variable does not match the real underlying type // | 'bool' | Panic if a 'bool' does not have a valid value ('true' or 'false') // | 'nan' | Panic if a 'nan' is used in a float arithmetic operation // | 'sanity' | Do a 'sanity' check (per function) // | 'null' | Panic on dereferencing some null pointers // If 'what' is null or empty, every options are will be affected. #[AttrUsage(AttributeUsage.All | AttributeUsage.File), AttrMulti] attr Safety(what: string, value: bool) // Enable/Disable a given function optimization. // Options are: // | 'bytecode' | Enable/Disable bytecode optimization for the function // | 'backend' | Enable/Disable backend machine code optimization for the function (llvm only) // If 'what' is null or empty, every options will be affected. #[AttrUsage(AttributeUsage.Function | AttributeUsage.File), AttrMulti] attr Optimize(what: string, value: bool) #[AttrUsage(AttributeUsage.All | AttributeUsage.File)] attr AllowOverflow(value: bool) // Warning behavior for [[Warning]] attribute enum WarningLevel: u8 { Enable // Enable the given warning Disable // Disable the given warning Error // Force the given warning to be raised as an error } // Change the behavior of a given warning or list of warnings. // For example: // ```swag // #[Swag.Warning("Wrn0006", Swag.WarningLevel.Error) // #[Swag.Warning("Wrn0002|Wrn0006", Swag.WarningLevel.Disable) // #global #[Swag.Warning("Wrn0005", Swag.WarningLevel.Enable)] // ``` // You can also change the warning behaviors for the whole module in your [[BuildCfg]] #[AttrUsage(AttributeUsage.All | AttributeUsage.File), AttrMulti] attr Warning(what: string, level: WarningLevel) #[AttrUsage(AttributeUsage.All)] attr Match(what: string, value: bool) attr Strict() attr Global() attr Align(value: u8)

Scoping

Namespace

Namespaces in Swag

Namespaces in Swag provide a mechanism to organize and encapsulate symbols such as functions, variables, and types within a specific scope. By grouping related symbols together under a namespace, you can avoid naming conflicts and make your codebase more modular and maintainable. Symbols within a namespace are accessible only through the namespace unless explicitly made available outside of it.

// Define a simple namespace 'A' namespace A { // Function 'a' is defined within the namespace 'A'. func a() => 1 }

Nested Namespaces

Swag allows you to create nested namespaces, enabling hierarchical organization of symbols. In the following example, namespace C is nested inside B, which is itself nested inside A. This structure facilitates even finer control and organization, particularly useful in large projects.

// Define a nested namespace 'A.B.C' namespace A.B.C { // Function 'a' is defined within the nested namespace 'A.B.C'. func a() => 2 } #test { // Accessing functions using their fully qualified namespace paths. @assert(A.a() == 1) // Calls the 'a' function from namespace 'A' @assert(A.B.C.a() == 2) // Calls the 'a' function from nested namespace 'A.B.C' }

Using using with Namespaces

The using keyword allows you to import symbols from a namespace into the current scope, eliminating the need to fully qualify those symbols with the namespace name. This makes the code more concise and improves readability, particularly when working with deeply nested namespaces.

using namespace Private { const FileSymbol = 0 // A constant defined within the 'Private' namespace } const B = Private.FileSymbol // Accessing 'FileSymbol' with the full namespace path const C = FileSymbol // Direct access to 'FileSymbol' thanks to the 'using' directive

Private Scopes with private

In addition to named namespaces, Swag allows you to define a private scope using the private keyword. A private scope creates a unique, unnamed namespace that restricts access to the enclosed symbols to the current file, effectively making them private. This is particularly useful for isolating symbols that should not be accessible outside the file.

private { const OtherSymbol = 0 // A constant defined within an unnamed private scope } const D = OtherSymbol // Direct access to 'OtherSymbol' as it is private to the file

Exporting Symbols

By default, all symbols defined in a Swag source file are exported and can be accessed by other files within the same module. However, using private scopes or explicit namespaces provides a layer of protection against unintentional name conflicts, particularly in larger projects where different files may define symbols with similar or identical names.

Defer

defer Statement

The defer statement allows you to specify an expression that will be automatically executed when the current scope is exited. Since defer operates purely at compile time, the deferred expression is not evaluated until the block of code in which it is defined is left. This feature is useful for managing resources, ensuring cleanup operations, and maintaining code clarity.

#test { var v = 0 defer @assert(v == 1) // Ensures that v equals 1 when leaving the scope. v += 1 // Increment v by 1. // When the scope is exited, the deferred expression will be executed here, verifying that v is 1. }

Defer in a Block

The defer statement can also encapsulate multiple expressions within a block. This allows you to group together operations that should all be executed upon exiting the scope, maintaining the integrity of your logic and ensuring that all necessary actions are performed.

#test { var v = 0 defer { v += 10 // Increment v by 10. @assert(v == 15) // Ensure v equals 15 after the block execution. } v += 5 // Increment v by 5. // Upon scope exit, the defer block is executed, adding 10 to v and ensuring the final value is 15. }

Defer with Control Flow

The defer expression is executed whenever the scope is exited, including in the presence of control flow statements like return, break, or continue. This behavior makes defer particularly advantageous for scenarios that require guaranteed execution of specific operations, such as resource cleanup or ensuring that certain conditions are met, regardless of how the code block is terminated.

#test { var G = 0 for 10 { defer G += 1 // Ensure G is incremented each loop iteration, even if the loop is exited early. if G == 2: break // Exit the loop when G equals 2. Defer will execute before breaking out. // The defer expression is executed at the end of each iteration. } @assert(G == 3) // Check that G has been correctly incremented, even after the loop is broken. }

Defer Execution Order

When multiple defer statements are declared, they are executed in the reverse order of their declaration. This ensures that the most recently deferred operation occurs first upon scope exit, providing a predictable and manageable flow of operations.

#test { var x = 1 defer @assert(x == 2) // Executed second, after x is multiplied by 2. defer x *= 2 // Executed first, doubling the value of x. // The deferred statements execute in reverse, ensuring logical and predictable order. }

Example: Defer for Resource Management

A common use case for defer is in resource management, such as the creation and subsequent release of resources. By placing the release logic immediately after the creation logic, the code becomes more readable and ensures that resources are always properly managed, even in the event of an error or early exit.

#test { func createResource() => true func releaseResource(resource: *bool) = dref resource = false func isResourceCreated(b: bool) => b var resource = false for 10 { resource = createResource() defer { @assert(resource.isResourceCreated()) // Validate that the resource was created. releaseResource(&resource) // Release the resource when exiting the loop. } if #index == 2: break // Exit for early, defer block still ensures resource release. } @assert(!resource.isResourceCreated()) // Ensure the resource is no longer created post-loop. }

Example: Defer in Error Handling

In more complex functions, defer proves invaluable for ensuring that resources are cleaned up reliably, even in the presence of errors or early returns. This pattern is essential for writing robust, error-resilient code that gracefully handles failure scenarios while ensuring that all necessary cleanup is performed.

#test { func createResource() => true func releaseResource(resource: *bool) = dref resource = false func isResourceCreated(b: bool) => b func performTask() -> bool { var resource = createResource() defer releaseResource(&resource) // Guarantee resource release on function exit. if !resource.isResourceCreated() { // Handle error: resource wasn't created. Defer block still ensures cleanup. return false } // Perform other tasks here... // If an error occurs, the defer block will release the resource. return true } let success = performTask() @assert(success) // Ensure that the task was successful and resources were managed correctly. // The resource is released correctly regardless of the function's outcome. }

Using

using with Enums and Namespaces

The using statement allows you to bring the scope of a namespace, struct, or enum into the current scope. This makes it possible to reference members directly without the need for full qualification. For example, when working with enums, this can simplify the code by removing the need to constantly prefix enum values with the enum type name.

#test { enum RGB { R, G, B } @assert(RGB.R == 0) // Accessing the enum member with full qualification. using RGB // Bringing the enum members into the current scope. @assert(G == 1) // 'G' is now directly accessible without the 'RGB.' prefix. }

using with Variables

The using statement can also be applied to variables, particularly those of struct types. This allows you to access the fields of a struct directly within the current scope, eliminating the need to reference the variable name each time you access a field. This is particularly useful for reducing code verbosity and improving readability.

#test { struct Point { x: s32, y: s32 } var pt: Point // Declaring a variable of struct type 'Point.' using pt // Bringing the fields of 'pt' into the current scope. x = 1 // Direct access to 'x' without needing 'pt.x'. y = 2 // Direct access to 'y' without needing 'pt.y'. @assert(pt.x == 1) // Verifying that 'x' was set correctly. @assert(pt.y == 2) // Verifying that 'y' was set correctly. }

Declaring Variables with using

You can declare a variable with the using keyword, which immediately brings the variable’s fields into the current scope. This approach can streamline your code by allowing direct access to struct fields without the need to prefix them with the variable name, making the code cleaner and more concise.

#test { struct Point { x: s32, y: s32 } using var pt: Point // Declare 'pt' and bring its fields into the current scope. x = 1 // Direct access to 'x' without specifying 'pt'. y = 2 // Direct access to 'y' without specifying 'pt'. @assert(pt.x == 1) // Ensure that 'x' was correctly set. @assert(pt.y == 2) // Ensure that 'y' was correctly set. }

using in Function Parameters

When applied to function parameters, using allows fields of a struct to be accessed directly within the function, similar to how a this pointer works in C++. This can simplify function code by eliminating the need to repeatedly dereference a pointer or reference a parameter name, making the function logic clearer and easier to follow.

#test { struct Point { x: s32, y: s32 } func setOne(using point: *Point) { // Access the fields of 'point' directly without prefixing with 'point->'. x, y = 1 } var pt: Point setOne(&pt) // Call 'setOne' and modify 'pt' directly. @assert(pt.x == 1) // Validate that 'x' was set correctly. @assert(pt.y == 1) // Validate that 'y' was set correctly. // UFCS (Uniform Function Call Syntax) allows calling the function as if it were a method. pt.setOne() // Equivalent to 'setOne(&pt)'. @assert(pt.x == 1) // Ensure 'x' remains correct. @assert(pt.y == 1) // Ensure 'y' remains correct. }

using with Struct Fields

The using statement can also be applied to a field within a struct. This allows the fields of a nested struct to be accessed as if they were part of the containing struct. This feature is especially useful when working with inheritance or composition, enabling cleaner and more intuitive code by removing unnecessary layers of field access.

#test { struct Point2 { x, y: s32 // Define two fields, 'x' and 'y'. } struct Point3 { using base: Point2 // Bring 'Point2' fields into 'Point3' scope. z: s32 // Define an additional field 'z'. } // The 'base' fields can now be referenced directly through 'Point3'. var value: Point3 value.x = 0 // Direct access to 'x', equivalent to 'value.base.x = 0'. value.y = 0 // Direct access to 'y', equivalent to 'value.base.y = 0'. value.z = 0 // Access 'z' directly, as it is part of 'Point3'. @assert(&value.x == &value.base.x) // Validate that 'x' refers to the correct memory location. @assert(&value.y == &value.base.y) // Validate that 'y' refers to the correct memory location. // The compiler can automatically cast 'Point3' to 'Point2' due to the `using` statement. func set1(using ptr: *Point2) { x, y = 1 // Direct access to 'x' and 'y' fields. } set1(&value) // Automatic cast to 'Point2' and modify 'value'. @assert(value.x == 1) // Confirm 'x' was correctly set. @assert(value.y == 1) // Confirm 'y' was correctly set. @assert(value.base.x == 1) // Ensure 'base.x' was updated. @assert(value.base.y == 1) // Ensure 'base.y' was updated. }

With

with Statement

The with statement is designed to reduce repetition by allowing you to access the fields and methods of a variable or object within a specified scope. Inside a with block, you can use the . prefix to refer to the fields or methods of the specified object, making the code more concise and easier to read.

struct Point { x, y: s32 } impl Point { mtd setOne() { x, y = 1 // Set both x and y to 1 within the Point instance. } }

with on a Variable

The with statement can be used with a variable to streamline access to its fields and methods, eliminating the need to repeatedly reference the variable name. This makes the code cleaner and reduces clutter, especially when working with objects that have multiple fields or methods.

#test { var pt: Point // Declare a variable of type Point. with pt { .x = 1 // Equivalent to pt.x = 1, sets the x field. .y = 2 // Equivalent to pt.y = 2, sets the y field. } @assert(pt.x == 1) // Verify that x was set correctly. @assert(pt.y == 2) // Verify that y was set correctly. }

with with Function Calls

The with statement also simplifies function calls on an object or struct by allowing direct invocation of methods and access to fields within the with block. This approach helps maintain cleaner and more intuitive code by reducing the repetition of the object or variable name.

#test { var pt: Point // Declare a variable of type Point. with pt { .setOne() // Equivalent to pt.setOne(), sets both x and y to 1. .y = 2 // Modify the y field directly within the block. @assert(.x == 1) // Equivalent to pt.x == 1, verifies that x is set to 1. @assert(.y == 2) // Equivalent to pt.y == 2, verifies that y is set to 2. } @assert(pt.x == 1) // Confirm that x remains correct after the with block. @assert(pt.y == 2) // Confirm that y remains correct after the with block. }

with with a Namespace

The with statement can also be applied to a namespace, allowing you to call functions or access constants within that namespace without needing to fully qualify the names. This is particularly useful when working with large namespaces or when multiple calls to namespace members are required.

#test { with NameSpace { .inside0() // Equivalent to NameSpace.inside0(), calls the inside0 function. .inside1() // Equivalent to NameSpace.inside1(), calls the inside1 function. } }

with with Variable Declaration

In addition to existing variables, the with statement can be used directly with variable declarations. This allows you to immediately work with the fields of the newly declared variable within the scope of the with block, streamlining initialization and setup tasks.

#test { with var pt = Point{1, 2} // Declare and initialize 'pt' with x=1, y=2. { .x = 10 // Modify x within the block. .y = 20 // Modify y within the block. } @assert(pt.x == 10 and pt.y == 20) // Ensure both fields are correctly updated. } #test { with var pt: Point // Declare 'pt' without initialization. { .x = 10 // Set x to 10 within the block. .y = 20 // Set y to 20 within the block. } @assert(pt.x == 10 and pt.y == 20) // Ensure fields are set as expected. }

with with an Assignment Statement

The with statement can also be used with an assignment, allowing you to immediately access and modify the fields of the newly assigned value. This can be particularly helpful in scenarios where you want to initialize or adjust an object’s fields immediately after creation or assignment.

#test { var pt: Point // Declare a variable of type Point. with pt = Point{1, 2} // Assign a new Point instance to 'pt'. { .x = 10 // Modify x within the block. .y = 20 // Modify y within the block. } @assert(pt.x == 10 and pt.y == 20) // Ensure fields are updated correctly. } namespace NameSpace { func inside0() {} // Define a function inside the namespace. func inside1() {} // Define another function inside the namespace. }

Type reflection

Types as Values in Swag

In Swag, types are treated as first-class values that can be inspected and manipulated at both compile-time and runtime. This powerful feature enables advanced metaprogramming capabilities, allowing developers to write more flexible and reusable code. The primary intrinsics for interacting with types are #typeof and @kindof, which provide the ability to introspect and work with types dynamically.

Using #typeof to Inspect Types

The #typeof intrinsic allows you to retrieve the type information of an expression. When an expression explicitly represents a type, you can also directly use the type itself. This is particularly useful for type inspection and validation at compile time.

#test { // Using #typeof to retrieve the type information of the basic type 's8' let ptr1 = #typeof(s8) @assert(ptr1.name == "s8") // Verifies that the name of the type is 's8' @assert(ptr1 == s8) // Confirms that the retrieved type matches 's8' // Retrieving the type of another basic type 's16' using #typeof let ptr2 = #typeof(s16) @assert(ptr2.name == "s16") @assert(ptr2 == s16) // Directly using the type 's32' without #typeof let ptr3 = s32 @assert(ptr3.name == "s32") @assert(ptr3 == #typeof(s32)) // Another example of direct type usage with 's64' let ptr4 = s64 @assert(ptr4.name == "s64") @assert(ptr4 == s64) }

Understanding the Result of #typeof

The result of the #typeof intrinsic is a constant pointer to a Swag.TypeInfo structure. This structure serves as a type descriptor, providing detailed information about the type it describes. The Swag.TypeInfo is an alias for the typeinfo type, and each type in Swag corresponds to a specific TypeInfo structure found in the Swag namespace, which is part of the compiler runtime.

Note

You can explore all available type descriptors in the runtime documentation on the Swag website.

#test { // Using #typeof on a bool type, which is a native type let ptr = bool @assert(#typeof(ptr) == #typeof(const *Swag.TypeInfoNative)) // The `#type` keyword is used when the expression might be ambiguous, such as with arrays. // This ensures that the compiler understands the expression as a type. let ptr1 = #type [2] s32 @assert(#typeof(ptr1) == #typeof(const *Swag.TypeInfoArray)) @assert(ptr1.name == "[2] s32") // Using #typeof on an array literal let ptr2 = #typeof([1, 2, 3]) @assert(#typeof(ptr2) == #typeof(const *Swag.TypeInfoArray)) @assert(ptr2.name == "const [3] s32") }

Working with TypeInfo Structures

The TypeInfo structure includes a kind field that identifies the specific category of the type, such as native type, pointer, array, or struct. This kind field is crucial when dealing with types in a more abstract or generic manner, as it allows you to differentiate between various kinds of types and handle them appropriately.

#test { // Checking the 'kind' field of the typeinfo for 'f64', which should be a native type let typeOf = f64 @assert(typeOf.kind == Swag.TypeInfoKind.Native) // Evaluating these type checks at compile time using Swag #assert #typeof(*u8).kind == TypeInfoKind.Pointer // Ensures the type is recognized as a pointer #assert #typeof([1, 2, 3]).kind == TypeInfoKind.Array // Ensures the type is recognized as an array #assert #typeof({1, 2, 3}).kind == TypeInfoKind.Struct // Ensures the type is recognized as a struct }

#decltype

The #decltype intrinsic performs the reverse operation of #typeof or @kindof. It converts a typeinfo structure back into an actual compiler type. This is useful when you need to dynamically determine the type of a variable based on compile-time information and then use that type within your code.

#test { // Using #decltype to declare a variable of type s32 based on its typeinfo var x: #decltype(#typeof(s32)) #assert #typeof(x) == s32 // Confirms that the variable 'x' is indeed of type 's32' }

Using #decltype with Compile-Time Expressions

#decltype can evaluate a compile-time constant expression (constexpr) that returns a typeinfo to determine the actual type. This is particularly powerful when the type depends on compile-time logic, allowing for dynamic yet type-safe programming patterns.

#test { // A function that returns a typeinfo based on a compile-time condition #[Swag.ConstExpr] func getType(needAString: bool) -> typeinfo { if needAString: return string else: return s32 } // Using #decltype to determine the type of 'x' based on the compile-time condition var x: #decltype(getType(needAString: false)) #assert #typeof(x) == s32 // Confirms that 'x' is of type s32 x = 0 // Another example with the condition evaluating to true var x1: #decltype(getType(needAString: true)) #assert #typeof(x1) == string // Confirms that 'x1' is of type string x1 = "0" }

Error management and safety

Error management

A function marked with throw indicates that it can return an error by invoking the throw keyword, followed by an error value. The error value is a struct, which encapsulates the error details. If an error occurs, the caller has the option to either halt execution and propagate the error using try, or handle the error explicitly using the @err() intrinsic.

It's crucial to understand that these are not exceptions in the traditional sense. Consider throw as a specialized form of return that carries an error value.

Using throw with Custom Errors

A function that has the potential to return an error must be annotated with the throw keyword. This annotation signals that the function can raise an error using the throw statement. When an error is thrown, it is represented as a structured value, typically using a custom-defined struct to encapsulate the error information.

// Here, we define a custom error type named `MyError`. This custom error extends a default runtime base error provided by the language. struct MyError { // The custom error inherits from the base error type `Swag.BaseError`. // The base error type includes common fields such as an error 'message', which can be useful for debugging or error reporting. using base: Swag.BaseError }

When a function encounters an error and exits early, the actual return value is not what the function was originally intended to return. Instead, the function returns the default value for its return type. For example, if the return type is an integer, the default value would typically be 0.

// The function 'count()' is defined to take a string parameter named 'name' and return an unsigned 64-bit integer (u64). // The 'throw' keyword at the end of the function's signature indicates that this function can potentially raise an error. func count(name: string) -> u64 throw { // Inside the function, we first check if the input parameter 'name' is null (i.e., it has no value assigned to it). if name == null { // If 'name' is null, we raise (throw) an error of type `MyError`. // The error is initialized with a string message "null pointer" to describe the problem. // When an error is thrown, the function will not proceed to return the intended value. // Instead, it will return 0, which is the default value for the u64 type. throw MyError{"null pointer"} // Error is thrown with the message: "null pointer" } // If 'name' is not null, the function proceeds to count the number of characters in the string. // The '@countof(name)' function is used to determine this count, and the result is returned. return @countof(name) // Return the count of characters in the string }

Handling Errors with catch and @err()

When calling a function that can throw an error, it's essential to handle the error appropriately. This is where the catch keyword comes into play. The error can be caught using catch, and its value can be tested (or ignored) using the @err() intrinsic. If an error is caught, it is dismissed, allowing the program to continue execution from the point where the error was handled.

func myFunc() { // Attempt to call the `count()` function. If `count()` throws an error, // `catch` will intercept it. The function will then return the default value // of the return type (in this case, 0) and assign it to `cpt`. let cpt = catch count("fileName") // Immediately after catching the error, use `@err()` to check if an error occurred. if @err() != null { // `@err()` returns an 'any' type, representing the caught error. // Here, we assert that the error is of type `MyError` to ensure correct error handling. @assert(@err() == MyError) // Validate that the error is indeed a `MyError`. // Also, assert that the value returned by `count()` is the default value (0 in this case), // which happens when an error is thrown. @assert(cpt == 0) // Inform the user that an error was encountered during the execution. @print("An error was raised") return // Exit the function early since an error was handled. } // If no error was caught, the function would proceed normally from this point. }

Error Handling with trycatch

The trycatch construct provides an alternative way to handle errors. It allows the function to dismiss the error and exit gracefully, returning the default value if necessary. This approach ensures that no error is propagated to the caller, effectively containing the error within the current function.

func myOtherFunc() { // Attempt to call the `count()` function. If it throws an error, // `trycatch` catches the error, assigns the default value to `cpt1`, // and the function continues without propagating the error. var cpt1 = trycatch count("fileName") // The above `trycatch` block is equivalent to using `catch` followed by an error check: var cpt2 = catch count("filename") if @err() != null: return // If an error is caught, exit the function silently }

Propagating Errors with try

The try keyword allows a function to halt its execution and propagate any encountered errors to its caller. The function using try must be annotated with throw, indicating that it can pass errors up the call stack.

In this example, if the function myFunc1 encounters an error, it will propagate that error to its caller, requiring the caller to handle it.

func myFunc1() throw { // Attempt to call the `count()` function. If it raises an error, // `try` will automatically propagate the error to the caller of `myFunc1`. var cpt = try count("filename") // Propagate the error if one occurs }

This behavior is equivalent to the following code, which uses catch to catch the error and then explicitly re-throws it.

func myFunc2() throw { // Call `count()` and catch any error that might be raised. var cpt = catch count("filename") if @err() != null { // If an error is caught, re-throw the same error, propagating it to the caller. throw @err() // Re-throw the caught error } }

Forcing Panic with assume

The assume keyword allows the caller to force a panic if an error occurs, rather than handling or propagating the error. This is useful in scenarios where you expect the code to run without errors, and any error should result in an immediate halt.

Note

In release builds, the assume behavior can be disabled, which may lead to undefined behavior if an error occurs.

func myFunc3() { // If the `count()` function throws an error, the program will panic and terminate, // displaying an error message. The error is not propagated or handled. var cpt = assume count("filename") // Panic on error }
Warning

If an error is not caught, Swag will panic at runtime. This is because the top-level caller always assumes safe execution, ensuring that unhandled errors result in a program halt.

Implicit assume

Instead of using throw, you can annotate the entire function with assume. This treats the entire function as if every error is critical, causing an automatic panic if any error occurs.

func myFunc3A() assume { // Since the function is annotated with `assume`, any error thrown by `count()` will // cause an immediate panic, without the need for explicit `assume` statements. var cpt = count("filename") // Implicit `assume` // Additional calls to `count()` are also covered by the implicit `assume`. var cpt1 = count("filename") // Implicit `assume` }

Blocks in Error Handling

Blocks can be used in place of a single statement to group multiple operations together in error-handling constructs like try, assume, catch, and trycatch. These blocks do not create a new scope but allow you to execute several related operations under a single error-handling strategy.

func myFunc4() throw { // Using 'try' with a block to propagate errors from multiple operations. try { var cpt0 = count("filename") var cpt1 = count("other filename") } // Using 'assume' with a block to enforce a panic on any error occurring in the block. assume { var cpt2 = count("filename") var cpt3 = count("other filename") } // Using 'catch' with a block to catch and dismiss errors, proceeding with execution without handling or propagating the error. // In this context, checking '@err()' is irrelevant as the errors are dismissed. catch { var cpt4 = count("filename") var cpt5 = count("other filename") } // Using 'trycatch' with a block to catch errors and exit immediately, without propagating or handling the error message. trycatch { var cpt6 = count("filename") var cpt7 = count("other filename") } }

Implicit try

When a function is annotated with throw, any function calls within it that can raise errors will implicitly use try unless explicitly overridden. This implicit behavior reduces verbosity in scenarios where specific error handling is not required.

#test { // Define a function 'mySubFunc2' that throws an error. func mySubFunc2() throw { throw MyError{"error from mySubFunc2"} // Raise a custom error } // Define another function 'mySubFunc3' that also throws an error. func mySubFunc3() throw { throw MyError{"error from mySubFunc3"} // Raise a custom error } // Define 'mySubFunc1', which is marked with 'throw' indicating it can propagate errors. func mySubFunc1() throw { // When calling 'mySubFunc2()', there's no need for an explicit 'try' since 'mySubFunc1' // is marked with 'throw'. The 'try' is implicit, reducing the need for additional syntax. mySubFunc2() // Implicit 'try' // However, you can still use an explicit 'try' if specific error handling is desired. try mySubFunc3() // Explicit 'try' } // Catch the error raised by 'mySubFunc1' and confirm that it is of type 'MyError'. catch mySubFunc1() @assert(@err() == MyError) // Ensure the error is of type 'MyError' }

The error struct

As discussed, the error value is a struct. This allows you to add specific error parameters, such as line and column numbers for syntax errors.

struct SyntaxError { using base: Swag.BaseError line, col: u32 }
Warning

Ensure that references to external values (e.g., string, any) remain valid throughout the error's lifecycle. The runtime will manage complex types, so it's recommended to store such values in the heap or a dedicated allocator within the current context.

Using defer for Controlled Cleanup

The defer statement schedules a block of code to be executed when the function exits, whether it's through a normal return or due to an error being thrown. Since throwing an error is functionally similar to returning, defer behaves consistently in both cases.

defer can be customized with specific modes (#err or #noerr) to control its execution based on the function's exit state:

defer<err> Executes only when an error is raised via throw.
defer<noerr> Executes only when the function returns normally without errors.
defer Executes regardless of how the function exits (either by returning normally or by throwing an error).
var g_Defer = 0 // A global variable to track the execution of defer statements // A function that raises an error based on the provided input func raiseError() throw { throw MyError{"error"} // Raise a custom error } // A function that demonstrates the use of defer with different modes func testDefer(err: bool) throw { // Schedules this block to execute only if an error is raised. defer<err> g_Defer += 1 // Increment if an error occurs // Schedules this block to execute only if the function exits without an error. defer<noerr> g_Defer += 2 // Increment if no error occurs // Schedules this block to execute regardless of whether an error occurs. defer g_Defer += 3 // Increment regardless of error state if err: raiseError() // If 'err' is true, raise an error } #test { // Test case where an error is raised g_Defer = 0 catch testDefer(true) // Execute the function with error condition @assert(g_Defer == 4) // Expect g_Defer to be 4 (1 + 3) since only 'defer<err>' and the general 'defer' executed // Test case where no error is raised g_Defer = 0 catch testDefer(false) // Execute the function without error condition @assert(g_Defer == 5) // Expect g_Defer to be 5 (2 + 3) since only 'defer<noerr>' and the general 'defer' executed }

Safety

Safety Checks in Swag

Swag provides various safety checks that can be enabled at different granularity levels—module, function, or even individual instruction—using the #[Swag.Safety] attribute.

These safety checks are designed to prevent common programming errors by triggering panics during unsafe operations, such as overflows, invalid math operations, or out-of-bounds access.

You can also configure safety checks globally based on the build configuration using buildCfg.safetyGuards.

Note

Swag offers four predefined build configurations: debug, fast-debug, fast-compile, and release. Safety checks are enabled by default in debug and fast-debug, but they are disabled in fast-compile and release for performance reasons.

Overflow Safety

#[Swag.Safety("overflow", true)]

When overflow safety is enabled, Swag will panic if arithmetic operations overflow or if bits are lost during an integer conversion.

Operators that can cause overflows include: + - * << >> and their compound assignments += -= *= <<= >>=.

#test { var x = 255'u8 // Initialize x with the maximum value for u8 // x += 1 // Uncommenting this will cause a panic due to overflow }
Disabling Overflow Safety with #over

If an overflow is expected and should not cause a panic, the #over modifier can be used with the operation to bypass the safety check.

#test { var x = 255'u8 // Initialize x with the maximum value for u8 x += #over 1 // This will wrap around without causing a panic @assert(x == 0) // Assert that x has wrapped around to 0 }
Global Overflow Safety Control

To disable overflow safety checks globally within a scope, use #[Swag.AllowOverflow(true)]. This prevents overflows from causing panics for all operations in the scope.

#[Swag.AllowOverflow(true)] #test { var x = 255'u8 // Initialize x with the maximum value for u8 x += 1 // Overflow occurs, but no panic due to global setting @assert(x == 0) // Assert that x has wrapped around to 0 }
Promoting Operations to Prevent Overflow

For operations involving 8-bit or 16-bit integers, you can use the #prom modifier to promote the operation to 32-bit, thereby avoiding overflow by widening the operand types.

#test { let x = 255'u8 + #prom 1 // Promote the addition to 32-bit to avoid overflow @assert(x == 256) // Assert that the result is 256 @assert(#typeof(x) == u32) // Assert that the type of x is u32 }

Information Loss During Casting

Swag checks for potential information loss during type casting operations, such as when converting between different integer types.

#test { let x1 = 255'u8 // Initialize x1 with the maximum value for u8 // var y0 = cast(s8) x1 // This would cause a panic because 255 cannot be // represented as s8 let y1 = cast<overflow>(s8) x1 // Use #over to bypass safety checks and allow this // cast @assert(y1 == -1) // Assert that y1 is -1 after wrapping let x2 = -1's8 // Initialize x2 with the minimum value for s8 // var y2 = cast(u8) x2 // This would cause a panic because x2 is negative let y2 = cast<overflow>(u8) x2 // Use #over to bypass safety checks @assert(y2 == 255) // Assert that y2 is 255 after wrapping }
Disabling Overflow Safety Globally

Safety checks for overflow can be globally disabled, allowing operations that would typically panic due to overflow to proceed normally.

#[Swag.AllowOverflow(true)] #test { var x = 255'u8 // Initialize x with the maximum value for u8 x += 255 // x becomes 254 after wrapping x += 1 // x becomes 255 x >>= 1 // x becomes 127 after right shift @assert(x == 127) // Assert that x is 127 }

Dynamic Cast Type Safety

#[Swag.Safety("dyncast", true)]

Swag will panic if a cast from the any type to another type is invalid, ensuring type safety when working with dynamic types.

#test { let x: any = "1" // Initialize x with a string let y = cast(string) x // This is valid because the underlying type is correct // var z = cast(s32) x // This is invalid and will cause a panic // @assert(z == 0) }

Swag will also panic if casting from an interface to a pointer to struct that cannot be performed.

Array Bounds Checking

#[Swag.Safety("boundcheck", true)]

Swag will panic if an index is out of range when dereferencing a sized value such as an array, a slice, or a string.

#test { var x = [0, 1, 2] // Initialize an array var idx = 10 // Set an out-of-bounds index // @assert(x[idx] == 1) // This will cause a panic due to out-of-bounds access }
Safety When Indexing a Slice

Swag ensures that indexing operations on slices are within bounds to prevent runtime errors.

#test { let x: const [..] s32 = [0, 1, 2] // Initialize a slice var idx = 1 // Set a valid index @assert(x[idx] == 1) // Assert that x[1] is 1 idx += 9 // Move the index out of bounds // @assert(x[idx] == 1) // This will cause a panic due to out-of-bounds access }
Safety When Slicing a Sized Value

Swag will panic if a slice operation goes out of bounds, ensuring safe slicing of arrays or strings.

#test { var x: const [..] s32 = [0, 1, 2] // Initialize a slice // var slice = x[1..4] // This will cause a panic due to out-of-bounds access // @assert(slice[0] == 1) } #test { var x = "string" // Initialize a string var idx = 10 // Set an out-of-bounds index for slicing // var slice = x[0..idx] // This will cause a panic due to out-of-bounds slicing // @assert(slice[0] == 's') }

Math Safety

#[Swag.Safety("math", true)]

Swag will panic if certain math operations are invalid, such as division by zero or invalid arguments to math functions.

#test { var x = 1'f32 // Initialize x with a float value var y = 0'f32 // Initialize y with zero // var z = x / y // Division by zero will cause a panic // @print(z) }
Checking Invalid Math Intrinsic Arguments

Swag also checks for invalid arguments passed to certain math intrinsics, causing a panic if the arguments are unsupported or invalid.

#test { // The following operations will panic if the arguments are invalid: // @abs(-128) // Invalid argument for abs // @log(-2'f32) // Logarithm of a negative number is invalid // @log2(-2'f32) // Logarithm base 2 of a negative number is invalid // @log10(2'f64) // Logarithm base 10 of a negative number is invalid // @sqrt(-2'f32) // Square root of a negative number is invalid // @asin(-2'f32) // Arc sine out of valid range is invalid // @acos(2'f32) // Arc cosine out of valid range is invalid }

Switch Safety

#[Swag.Safety("switch", true)]

Swag will panic if a switch statement marked with #[Swag.Complete] does not cover all possible cases, ensuring exhaustive pattern matching.

#test { enum Color { Red, Green, Blue } // Define an enum with three values func colorToString(color: Color) -> string { // #[Swag.Complete] // Mark the switch as complete switch color { case Color.Red: return "Red" // Handle Red case Color.Green: return "Green" // Handle Green // If `Color.Blue` is not covered, this will cause a panic due to missing case } return "" // Return an empty string as a fallback } }

Boolean Safety

#[Swag.Safety("bool", true)]

Swag will panic if a boolean value is not either true (1) or false (0), enforcing strict boolean type safety.

#test { var b: u8 = 2 // Initialize b with an invalid boolean value // if b { ... } // This will panic because b is not a valid boolean }

NaN Safety

#[Swag.Safety("nan", true)]

Swag will panic if a floating-point NaN (Not a Number) is used in an operation, ensuring that NaNs do not propagate through calculations.

Compile-time evaluation

One of the most powerful features of Swag is its ability to execute everything at compile-time. This capability allows Swag to function not only as a compiled language but also as a scripting language, where the compiler effectively acts as an interpreter. This flexibility enables developers to leverage compile-time execution for tasks that traditionally require runtime evaluation, resulting in more efficient and versatile code.

Constexpr

Compile-Time Function Evaluation with #[Swag.ConstExpr]

The #[Swag.ConstExpr] attribute marks a function as capable of being evaluated during compile time. This enables the compiler to resolve the function's result at compile time, provided that all inputs are also known at compile time. This approach significantly optimizes the code by precomputing values and eliminating the need for these calculations at runtime.

Functions marked with #[Swag.ConstExpr] are ideal for returning constant values or performing operations that are determined before runtime, thereby improving efficiency.

// The function 'isThisDebug' is annotated with 'Swag.ConstExpr', indicating that // it can be evaluated during the compilation phase. Given that this function // consistently returns `true`, the result is established at compile time. #[Swag.ConstExpr] func isThisDebug() => true // This conditional block demonstrates how 'isThisDebug' can be utilized in a // compile-time context. Since 'isThisDebug' invariably returns `true`, the // condition `isThisDebug() == false` evaluates to `false`, and thus the compiler // will exclude the code inside the block from the final compilation. #if isThisDebug() == false { #error "this should not be called!" }

Recursive Compile-Time Evaluation

The #[Swag.ConstExpr] attribute can also be applied to more complex functions, including those that perform recursion. This allows such recursive functions to be entirely evaluated at compile time, effectively reducing runtime overhead and improving overall performance.

// This function 'factorial' calculates the factorial of a given number recursively. // By marking it with 'Swag.ConstExpr', the factorial is computed during compilation, // avoiding the need for runtime computation. #[Swag.ConstExpr] func factorial(x: s32) -> s32 { // Base case: return 1 when x equals 1 if x == 1: return 1 // Recursive case: multiply x by the factorial of (x - 1) return x * factorial(x - 1) } // The `#assert` directive ensures that 'factorial(4)' equals 24. As 'factorial' // is evaluated at compile time, this assertion is verified before execution begins. #assert factorial(4) == 24 // Evaluated at compile time

Compile-Time Constant Expressions

In this section, #[Swag.ConstExpr] is utilized to define a straightforward constant expression. The function returns a fixed value that the compiler resolves during compilation.

// The 'getMagicNumber' function returns a constant value of 42. // Since it's a compile-time constant expression, this value is resolved at compile time. #[Swag.ConstExpr] func getMagicNumber() -> s32 { return 42 } // The assertion checks that 'getMagicNumber()' equals 42, verified at compile time. #assert getMagicNumber() == 42

Compile-Time Conditional Logic

This example illustrates how the function isEven determines if a number is even. By marking it with #[Swag.ConstExpr], the compiler can perform this logic during the compilation phase.

// The 'isEven' function checks whether a number is even. // With the 'Swag.ConstExpr' annotation, this check occurs at compile time. #[Swag.ConstExpr] func isEven(x: s32) -> bool { return x % 2 == 0 } // This block only compiles if the number 4 is even. Since 4 is indeed even, // the error is not triggered, and the code compiles successfully. #if isEven(4) == false { #error "4 should be even!" }

Compile-Time Slice Operations

In this example, #[Swag.ConstExpr] is used to calculate the sum of elements within an array. The summation is performed during the compilation, optimizing runtime performance.

// The 'arraySum' function calculates the sum of all elements in an array. // Since it is a compile-time function, the sum is computed during the compilation phase. #[Swag.ConstExpr] func arraySum(arr: const [..] s32) -> s32 { var sum = 0 foreach val in arr: sum += val return sum } // The assertion verifies that the sum of the array [1, 2, 3, 4, 5] equals 15. // This is checked and confirmed at compile time. #assert arraySum([1, 2, 3, 4, 5]) == 15

Compile-Time Fibonacci Sequence

This example showcases how #[Swag.ConstExpr] enables a recursive function to compute the Fibonacci sequence at compile time, optimizing the execution.

// The 'fibonacci' function calculates the nth Fibonacci number recursively. // The result is computed during the compilation when marked with 'Swag.ConstExpr'. #[Swag.ConstExpr] func fibonacci(n: s32) -> s32 { if n <= 1: return n return fibonacci(n - 1) + fibonacci(n - 2) } // The 5th Fibonacci number, which is 5, is calculated during compilation and asserted here. #assert fibonacci(5) == 5

Compile-Time Bitwise Operations

Bitwise operations can also be evaluated at compile time using #[Swag.ConstExpr]. This example demonstrates how to check if a specific bit is set within a number.

// The 'isBitSet' function checks whether a particular bit is set in a number. // With 'Swag.ConstExpr', this operation is performed at compile time. #[Swag.ConstExpr] func isBitSet(num: s32, bit: s32) -> bool { return (num & (1 << bit)) != 0 } // The assertion verifies that the 3rd bit (0-indexed) of the number 8 is set. // Since 8 in binary is 1000, the check is resolved during compilation. #assert isBitSet(8, 3) == true

Run

Force Compile-Time Execution with #run

The #run directive allows you to invoke a function at compile time, even if that function is not marked with the #[Swag.ConstExpr] attribute. This powerful feature enables compile-time execution of any function, regardless of its original design or intent, whether it comes from external modules, system libraries, or is defined within your code.

// The function 'isThisRelease' is not marked with 'Swag.ConstExpr', which means // it is not specifically prepared for compile-time evaluation. func isThisRelease() => true // However, by using the `#run` directive, we force the function to be executed // at compile time. In this example, since 'isThisRelease()' returns `true`, the // condition `isThisRelease() == false` evaluates to `false`, and the code block // is excluded from the compilation process. #if #run isThisRelease() == false { #error "this should not be called!" }

This capability allows you to execute any function at compile time, whether it is a system function, a function from an external module, or a user-defined function.

// The function 'sum' is a regular function that sums a variable number of integers. // It is not explicitly marked for compile-time evaluation, as it lacks the // 'Swag.ConstExpr' attribute. func sum(values: s32...) -> s32 { var result = 0's32 foreach v in values: result += v return result } // Despite the absence of 'Swag.ConstExpr', we can still execute 'sum' at compile time // using the `#run` directive. The expression `#run sum(1, 2, 3, 4) + 10` is evaluated // during compilation, and the result is assigned to 'SumValue'. const SumValue = #run sum(1, 2, 3, 4) + 10 #assert SumValue == 20

#run Block

The #run directive can also be used in a block format. When placed inside a block, #run enables you to execute complex logic or initialize global variables at compile time. Multiple #run blocks can exist in a program, but the execution order is undefined, so care must be taken when relying on the order of these blocks.

An example of using #run to precompute global values at compile time.

// A global array 'G' that we intend to initialize using a more complex logic. var G: [5] f32 = undefined

The #run block below initializes the global array G with the values [1, 2, 4, 8, 16] at compile time, ensuring that the array is fully prepared before runtime.

#run { var value = 1'f32 for i in @countof(G) { G[i] = value value *= 2 } }

#test blocks are executed after #run blocks, even when run at compile time (during testing). This allows you to validate the correctness of compile-time calculations, as demonstrated below by verifying the contents of G.

#test { @assert(G[0] == 1) @assert(G[1] == 2) @assert(G[2] == 4) @assert(G[3] == 8) @assert(G[4] == 16) }

The flexibility of Swag allows it to function as a scripting language. In fact, if your project only contains #run blocks, you are effectively writing a script that runs during compilation.

#run Expression

The #run directive can also be used as an expression block. The return type of the block is inferred from the return statement within it.

#test { const Value = #run { var result: f32 for 10: result += 1 return result // The inferred type of 'Value' will be 'f32'. } #assert Value == 10.0 }

This technique can also be utilized to initialize a static array.

#test { const N = 4 const PowerOfTwo: [N] s32 = #run { var arr: [N] s32 for i in arr: arr[i] = 1 << cast(u32) i return arr } #assert PowerOfTwo[0] == 1 #assert PowerOfTwo[1] == 2 #assert PowerOfTwo[2] == 4 #assert PowerOfTwo[3] == 8 }

String initialization is another use case for #run blocks. The block below demonstrates how to construct a string at compile time. This is safe because the #run block creates a copy of the string, ensuring it persists beyond the block's execution.

#test { const MyString: string = #run { var str: [3] u8 str[0] = 'a' str[1] = 'b' str[2] = str[1] + 1 return cast(string) str } #assert MyString == "abc" }

#run blocks can also initialize plain old data (POD) structs. If necessary, you can enforce POD status on a struct by tagging it with #[Swag.ConstExpr].

#test { struct RGB { r, g, b: u8 } const White: RGB = #run { var rgb: RGB = undefined rgb.r = 255 rgb.g = rgb.r rgb.b = rgb.r return rgb } #assert White.r == 255 and White.g == 255 and White.b == 255 }
Note

It is possible to convert a complex struct (e.g., one that uses the heap) into a static array, provided the struct implements the opCount and opSlice methods. In this case, the resulting type will be a static array. The compiler will invoke opCount to determine the array size and opSlice to initialize its content. If the struct also implements opDrop, it will be called after the array conversion is complete.

Compiler instructions

#assert

The #assert directive is used to perform a static assertion during the compilation process. It ensures that a particular condition is true at compile time. If the condition evaluates to false, compilation will fail, providing an error message. This is particularly useful for enforcing compile-time invariants and validating assumptions within your code.

#assert true // This assertion always passes, so no error is triggered.

#defined(SYMBOL)

The #defined(SYMBOL) intrinsic checks if a given symbol exists within the current context at compile time. It returns true if the symbol is defined, and false otherwise. This is useful for conditional compilation, allowing you to verify the existence of variables, constants, or functions before using them.

#assert !#defined(DOES_NOT_EXISTS) // Ensures that the symbol 'DOES_NOT_EXISTS' is not defined. #assert #defined(Global) // Confirms that the symbol 'Global' is defined. var Global = 0 // Define a global variable 'Global'.

#if/#elif/#else

The #if/#elif/#else directives are used for static conditional compilation. They evaluate expressions at compile time and include or exclude code based on the result. This mechanism allows you to compile different sections of code based on predefined constants or compile-time conditions.

const DEBUG = 1 const RELEASE = 0 #if DEBUG { // This block is compiled because DEBUG is set to 1. } #elif RELEASE { // This block would be compiled if RELEASE were true and DEBUG were false. } #else { // This block is compiled if neither DEBUG nor RELEASE is true. }

#error/#warning

The #error and #warning directives allow you to raise compile-time errors and warnings, respectively. #error will cause the compilation to fail with a custom error message, while #warning will produce a warning message during compilation but will not stop the process. These directives are useful for enforcing compile-time checks and providing informative messages during the build process.

#if false { #error "this is an error" // Raises a compile-time error if this block is reached. #warning "this is a warning" // Raises a compile-time warning if this block is reached. }

#global

The #global directive can be placed at the top of a source file to apply global settings or attributes across the entire file. These directives control various aspects of the compilation and symbol visibility for the entire file.

Examples:

// Skip the content of the file (but it must be a valid Swag file). #global skip // All symbols in the file will be public (accessible from other modules). #global public // All symbols in the file will be internal (accessible only within the same module). #global internal // All symbols in the file will be placed within the namespace 'Toto'. #global namespace Toto // Conditional compilation for the entire file. #global #if DEBUG == true // Apply attributes to all declarations in the file. #global #[Swag.Safety("", true)] // Export the entire file for external usage. // This is similar to making everything public, but the file will also be copied // in its entirety to the public folder. #global export

#foreignlib

The #foreignlib directive is used to link with an external library during the compilation process. This allows your program to utilize functions, variables, and resources defined in the external library. The library name should be provided as a string.

Example:

#foreignlib "windows.lib"

This example links the program with the "windows.lib" library, allowing the use of Windows API functions and resources defined within that library.

Code inspection

#message Function

The #message function in Swag is a special hook that gets invoked by the compiler when specific events occur during the build process. This function allows you to intercept certain stages of compilation and execute custom actions or checks. The parameter of #message is a mask that specifies the compilation stage or event that should trigger the function. By utilizing these hooks, developers can gain deeper insights into the compilation process and perform additional processing when necessary.

Function Message Mask

For instance, when you use the Swag.CompilerMsgMask.SemFunctions flag, the #message function will be called each time a function within the module has been successfully typed. This means the function has been fully analyzed and its type information is available. Within the @compiler() interface, you can use the getMessage() method to retrieve details about the event that triggered the call, such as the function's name and type information.

#message(Swag.CompilerMsgMask.SemFunctions) { // Obtain the compiler interface to interact with the compilation process let itf = @compiler() // Retrieve the current compilation message let msg = itf.getMessage() // Given that the mask is `Swag.CompilerMsgMask.SemFunctions`, the message // pertains to a function, allowing us to safely cast the type information. let typeFunc = cast(const *Swag.TypeInfoFunc) msg.type // The message name, in this case, corresponds to the function's name being compiled. let nameFunc = msg.name // Example: Count functions whose names start with "XX" if @countof(nameFunc) > 2 and nameFunc[0] == 'X' and nameFunc[1] == 'X': G += 1 } // Global variable to count the number of functions starting with "XX" var G = 0 // Example functions to demonstrate the functionality of the `#message` hook func XXTestFunc1() {} func XXTestFunc2() {} func XXTestFunc3() {}

Semantic Pass Completion

The compiler will invoke the following #message function after the semantic pass has completed. The semantic pass occurs after all functions within the module have been parsed and typed. This stage is ideal for performing final checks or actions that need to consider the entire module's content.

#message(Swag.CompilerMsgMask.PassAfterSemantic) { // Verify that exactly 3 functions starting with "XX" were found during the compilation @assert(G == 3) }

Global Variables Message Mask

The #message function can also be triggered for each global variable in the module by using the Swag.CompilerMsgMask.SemGlobals flag. This allows you to process or validate each global variable as it is encountered by the compiler during the semantic analysis phase.

#message(Swag.CompilerMsgMask.SemGlobals) { // Retrieve the compiler interface let itf = @compiler() // Get the current message, which contains details about the global variable var msg = itf.getMessage() // Process the message as needed, for example, by analyzing the global variable's properties }

Global Types Message Mask

Similarly, the Swag.CompilerMsgMask.SemTypes flag triggers the #message function for each global type in the module, such as structs, enums, and interfaces. This allows you to inspect, analyze, or modify global types during compilation.

#message(Swag.CompilerMsgMask.SemTypes) { // Access the compiler interface let itf = @compiler() // Retrieve the current message, which contains information about the global type var msg = itf.getMessage() // Process the message as required, which could include analyzing type attributes or properties }

Meta programming

Swag provides the ability to construct and inject source code at compile time. This powerful feature allows you to dynamically generate code based on compile-time conditions or inputs, and have that code seamlessly integrated into the final program. The source code is supplied as a string, and it must be a valid Swag program.

This technique opens up possibilities for advanced metaprogramming, where code can be generated, modified, or extended during the compilation process, reducing redundancy and enabling more flexible, adaptive programs.

Ast

#ast Block

The #ast block is one of the simplest methods to generate Swag code dynamically at compile time. It allows you to write code that, when executed during compilation, produces a string. This string is then compiled in place as if it were written directly in the source code.

Basic #ast Usage

A #ast block can be as straightforward as a single expression that returns the string to be compiled. This feature is highly useful for injecting dynamically generated code during compilation.

#test { #ast "var x = 666" @assert(x == 666) // The variable 'x' is generated by the `#ast` block and initialized with the value 666. }

#ast Block with return

The #ast block can contain more complex logic, including multiple statements and an explicit return. The returned string will be compiled at the location of the #ast block.

#test { var cpt = 2 #ast { const INC = 5 return "cpt += " ++ INC // Generates the code 'cpt += 5', incrementing 'cpt' by the value of 'INC'. } @assert(cpt == 7) // The variable 'cpt' is incremented by 5 as generated by the `#ast` block. }

#ast for Structs and Enums

The #ast block can be used to dynamically generate the content of complex types like structs or enums. This approach is particularly valuable for creating code structures based on compile-time conditions or parameters.

#test { struct MyStruct { #ast { return "x, y: s32 = 666" // Generates two fields 'x' and 'y', both initialized to 666. } } var v: MyStruct @assert(v.x == 666) // Asserts that the generated field 'x' is correctly initialized to 666. @assert(v.y == 666) // Asserts that the generated field 'y' is correctly initialized to 666. }

#ast with Generics

The #ast block can be effectively used with generics, allowing for flexible and reusable code generation patterns. This can be particularly powerful when combined with static declarations.

#test { struct(T) MyStruct { #ast { return "x, y: " ++ #typeof(T).name // Generates fields 'x' and 'y' with the type of the generic parameter 'T'. } z: string // Additional static declaration that adds a field 'z' of type string. } var v: MyStruct'bool #assert #typeof(v.x) == bool // Asserts that the generated field 'x' is of type 'bool'. #assert #typeof(v.y) == bool // Asserts that the generated field 'y' is of type 'bool'. #assert #typeof(v.z) == string // Asserts that the static field 'z' is of type 'string'. var v1: MyStruct'f64 #assert #typeof(v1.x) == f64 // Asserts that the generated field 'x' is of type 'f64'. #assert #typeof(v1.y) == f64 // Asserts that the generated field 'y' is of type 'f64'. #assert #typeof(v1.z) == string // Asserts that the static field 'z' is of type 'string'. }

Constructing Strings in #ast

The #ast block requires a string-like value to be returned, which can be dynamically constructed. In this example, the string is manually built, although a more sophisticated method like Core.String is typically recommended.

#test { #[Swag.Compiler] func append(buf: ^u8, val: string) { var len = 0 while buf[len]: len += 1 @memcpy(buf + len, @dataof(val), cast(u64) @countof(val) + 1) } struct Vector3 { #ast { // Construct the code to compile in this local array var buf: [256] u8 append(buf, "x: f32 = 1\n") append(buf, "y: f32 = 2\n") append(buf, "z: f32 = 3\n") // Return the constructed code to the compiler return cast(string) buf } } var v: Vector3 @assert(v.x == 1) // Asserts that the generated field 'x' is initialized to 1. @assert(v.y == 2) // Asserts that the generated field 'y' is initialized to 2. @assert(v.z == 3) // Asserts that the generated field 'z' is initialized to 3. }

Real-World Example

The following is a practical example from the Std.Core module. It demonstrates how an #ast block can generate a structure where all the fields of another structure have their types converted to bool.

struct(T) IsSet { #ast { // A `StringBuilder` is used to manipulate dynamic strings. var str = StrConv.StringBuilder{} // We get the type of the generic parameter 'T' let typeof = #typeof(T) // Then we foreach all the fields, assuming the type is a struct (or this will not compile). // For each original field, we create one with the same name, but with a `bool` type. foreach f in typeof.fields: str.appendFormat("%: bool\n", f.name) // Then we return the constructed source code. // It will be used by the compiler to generate the content of the `IsSet` struct. return str.toString() } }

#ast at Global Scope

The #ast block can also be employed at the global scope to dynamically generate global variables, constants, or other declarations. This feature allows for a high degree of flexibility in defining global entities.

#ast { const value = 666 return "const myGeneratedConst = " ++ value // Generates a global constant 'myGeneratedConst' with the value 666. }

#placeholder Usage

When generating global symbols that may be referenced elsewhere in the code, it is necessary to use #placeholder. This directive informs Swag that the symbol will be generated later, preventing compilation errors when the symbol is referenced before it exists.

#placeholder myGeneratedConst // Declares that the symbol `myGeneratedConst` will be generated.

Here, thanks to the #placeholder, the #assert will wait for the symbol myGeneratedConst to be replaced with its actual value before performing the assertion.

#assert myGeneratedConst == 666 // Asserts that the generated constant 'myGeneratedConst' equals 666.

Compiler interface

The compileString() function within the @compiler() interface is another method to compile generated code. This function should be invoked at compile time, typically within a #message call.

Below is an example from the Std.Ogl module (an OpenGL wrapper), which utilizes #message to identify functions annotated with a specific user attribute, Ogl.Extension, and subsequently generates code for each identified function.

First, we define a new attribute that can be associated with functions.

#[AttrUsage(AttributeUsage.Function)] attr Extension()

Example of applying the custom attribute to OpenGL functions.

#[Extension] { func glUniformMatrix2x3fv(location: GLint, count: GLsizei, transpose: GLboolean, value: const *GLfloat); func glUniformMatrix2x4fv(location: GLint, count: GLsizei, transpose: GLboolean, value: const *GLfloat); func glUniformMatrix3x2fv(location: GLint, count: GLsizei, transpose: GLboolean, value: const *GLfloat); func glUniformMatrix3x4fv(location: GLint, count: GLsizei, transpose: GLboolean, value: const *GLfloat); func glUniformMatrix4x2fv(location: GLint, count: GLsizei, transpose: GLboolean, value: const *GLfloat); func glUniformMatrix4x3fv(location: GLint, count: GLsizei, transpose: GLboolean, value: const *GLfloat); }

The following will be used to track the functions with that specific attribute.

struct OneFunc { type: typeinfo name: string } #[Compiler] var g_Functions: Array'OneFunc

This #message will be called for each function of the Ogl module.

#message(CompilerMsgMask.SemFunctions) { let itf = @compiler() var msg = itf.getMessage() // If the function does not have our attribute, ignore it if !Reflection.hasAttribute(msg.type, Extension) return // Track all functions with the specified attribute g_Functions.add({msg.type, msg.name}) }

We will generate a glInitExtensions global function, so we register it as a placeholder.

#placeholder glInitExtensions

This code is called once all functions of the module have been typed, and it handles the main code generation process.

#message(CompilerMsgMask.PassAfterSemantic) { var builderVars: StringBuilder var builderInit: StringBuilder // Generate the `glInitExtensions` function builderInit.appendString("public func glInitExtensions()\n{\n"); // Visit all functions we have registered, i.e., all functions with the `Ogl.Extension` attribute. foreach e in g_Functions { let typeFunc = cast(const *TypeInfoFunc) e.type // Declare a lambda variable for that extension builderVars.appendFormat("var ext_%: %\n", e.name, typeFunc.name) // Make a wrapper function builderVars.appendFormat("public func %(", e.name) foreach p, i in typeFunc.parameters { if i != 0 builderVars.appendString(", ") builderVars.appendFormat("p%: %", i, p.pointedType.name) } if typeFunc.returnType == void builderVars.appendFormat(")\n{\n") else builderVars.appendFormat(")->%\n{\n", typeFunc.returnType.name) builderVars.appendFormat("\treturn ext_%(", e.name) foreach p, i in typeFunc.parameters { if i != 0 builderVars.appendString(", ") builderVars.appendFormat("p%", i) } builderVars.appendString(");\n}\n\n") // Initialize the variable with the getExtensionAddress builderInit.appendFormat("\text_% = cast(%) getExtensionAddress(@dataof(\"%\"))\n", e.name, typeFunc.name, e.name); } // Compile !! let itf = @compiler() var str = builderVars.toString() itf.compileString(str.toString()) builderInit.appendString("}\n"); str = builderInit.toString() itf.compileString(str.toString()) }

Documentation

The Swag compiler is capable of generating comprehensive documentation for all modules within a specified workspace.

To generate documentation for your workspace, use the following command:

swag doc -w:myWorkspaceFolder

Swag supports various documentation generation modes, which should be specified in the module.swg file within the Swag.BuildCfg structure.

#dependencies { #import "pixel" #run { let itf = @compiler() let cfg = itf.getBuildCfg() cfg.genDoc.kind = .Api // Specify the documentation generation mode } }
KindPurpose
Swag.DocKind.Api Generates an api documentation (all public symbols)
Swag.DocKind.Examples Generates a documentation like this one
Swag.DocKind.Pages Generates different pages, where each file is a page (a variation of Examples)

Markdown files

If the module contains markdown files with the .md extension, they will be processed as if they were Swag comments.

Format of comments

Paragraphs

// Everything between empty lines is considered to be a simple paragraph. Which // means that if you put several comments on several lines like this, they all // will be part of the same paragraph. // // This is another paragraph because there's an empty line before. // // This is yet another paragraph.
Result

Everything between empty lines is considered to be a simple paragraph. Which means that if you put several comments on several lines like this, they all will be part of the same paragraph.

This is another paragraph because there's an empty line before.

This is yet another paragraph.

Inside a paragraph, you can end of line with \ to force a break without creating a new paragraph.

// First line. // Second line is on first line.\ // But third line has a break before.
Result

First line. Second line is on first line.
But third line has a break before.

A paragraph that starts with --- is a verbatim paragraph where every blanks and end of lines are respected. The paragraph will be generated as is without any markdown change.

// --- // Even... // // ...empty lines are preserved. // // You end that kind of paragraph with another '---' alone on its line. // Note that **everything** is not bold, put printed 'as it is'. // ---
Result

Even... ...empty lines are preserved. You end that kind of paragraph with another '---' alone on its line. Note that **everything** is not bold, put printed 'as it is'.

Lists

You can create a list of bullet points with *.

// * This is a bullet point // * This is a bullet point // * This is a bullet point
Result
  • This is a bullet point
  • This is a bullet point
  • This is a bullet point
// - This is a bullet point // - This is a bullet point // - This is a bullet point
Result
  • This is a bullet point
  • This is a bullet point
  • This is a bullet point

You can create an ordered list by starting the line with a digit followed by a ..

// 1. This is an ordered list // 1. The digit itself does not matter, real numbers will be computed // 0. This is another one
Result
  1. This is an ordered list
  2. The digit itself does not matter, real numbers will be computed
  3. This is another one
Warning

Swag only supports single line list items. You cannot have complex paragraphs (or sub lists).

Definition Lists

You can add a definition title with the + character followed by a blank, and then the title. The description paragraph should come just after the title, with at least 4 blanks or one tabulation.

// + Title // This is the description. // + Other title // This is the other description.
Result

Title

This is the description.

Other title

This is the other description.

A description can contain complex paragraphs.

// + Title // This is an embedded list. // * Item1 // * Item2
Result

Title

This is an embedded list.

  • Item1
  • Item2

The description paragraph can contain some empty lines.

// + Other title // // This is the other description // on more than one line.
Result

Other title

This is the other description on more than one line.

Quotes

You can create a quote with >

// > This is a block quote on multiple // > lines. // > // > End of the quote.

This is a block quote on multiple lines.

End of the quote.

You can create a special quote by adding a title on the first line. There must be exactly one blank between > and the title, and the title case should be respected.

  • NOTE:
  • TIP:
  • WARNING:
  • ATTENTION:
  • EXAMPLE:
// > NOTE: // > This is the note content
Note

This is the note content

// > TIP: // > This is a tip.
Tip

This is a tip.

// > WARNING: // > This is the warning content // > // > Another paragraph
Warning

This is the warning content

Another paragraph

// > ATTENTION: The content of the quote can be written on the same line as the title
Attention

The content of the quote can be written on the same line as the title

// > EXAMPLE: // > In the 'module.swg' file, we have changed the 'example' title to be `"Result"` instead of `"Example"`.
Result

In the module.swg file, we have changed the example title to be "Result" instead of "Example".

Tables

You can create a table by starting a line with |. Each column must then be separated with |. The last column can end with |, but this is not mandatory.

// A table with 4 lines of 2 columns: // | boundcheck | Check out of bound access // | overflow | Check type conversion lost of bits or precision // | math | Various math checks (like a negative '@sqrt') | // | switch | Check an invalid case in a '#[Swag.Complete]' switch |
Result
boundcheck Check out of bound access
overflow Check type conversion lost of bits or precision
math Various math checks (like a negative @sqrt)
switch Check an invalid case in a #[Swag.Complete] switch

You can define a header to the table if you separate the first line from the second one with a separator like ---. A valid separator must have a length of at least 3 characters, and must only contain - or :.

// | Title1 | Title2 // | ------ | ------ // | Item1 | Item2 // | Item1 | Item2
Result
Title1Title2
Item1 Item2
Item1 Item2

You can define the column alignment by adding : at the start and/or at the end of a separator.

// | Title1 | Title2 | Title3 // | :----- | :----: | -----: // | Align left | Align center | Align right
Result
Title1Title2Title3
Align left Align center Align right

Code

You can create a simple code paragraph with three backticks before and after the code.

// ``` // if a == true // @print("true") // ```
Result
if a == true @print("true")

You can also create that kind of paragraph by simply indenting the code with four blanks or one tabulation.

// if a == false // @print("false")

And if you want syntax coloration, add swag after the three backticks. Only Swag syntax is supported right now.

// ```swag // if a == true // @print("true") // ```
Result
if a == true @print("true")

Titles

You can define titles with #, ## ... followed by a blank, and then the text. The real level of the title will depend on the context and the generated documentation kind.

// # Title 1 // ## Title 2 // ### Title 3 // #### Title 4 // ##### Title 5 // ###### Title 6

References

You can create an external reference with [name](link).

// This is a [reference](https://github.com/swag-lang/swag) to the Swag repository on GitHub.
Result

This is a reference to the Swag repository on GitHub.

Images

You can insert an external image with ![name](link).

// This is an image ![image](https://swag-lang/imgs/swag_icon.png).

Markdown

Some other markers are also supported inside texts.

// This is `inline code` with back ticks.\ // This is inline 'code' with normal ticks, but just for a single word (no blanks).\ // This is **bold**.\ // This is *italic*.\ // This is ***bold and italic***.\ // This is ~~strikethrough~~.\ // This character \n is escaped, and 'n' will be output as is.\
Result

This is inline code with back ticks.
This is inline code with normal ticks, but just for a single word (no blanks).
This is bold.
This is italic.
This is bold and italic.
This is strikethrough.
This character n is escaped, and n will be output as is.

Api

In Swag.DocKind.Api mode, swag will collect all public definitions to generate the documentation. Std.Core is an example of documentation generated in that mode.

The main module documentation should be placed at the top of the corresponding module.swg file.

// This is the main module documentation. #dependencies { }

Other comments need to be placed just before a function, struct or enum.

The first paragraph is considered to be the short description which can appear on specific parts of the documentation. So make it... short.

If the first line ends with a dot ., then this marks the end of the paragraph, i.e. the end of the short description.

// This first paragraph is the short description of function 'test1' // // This second paragraph should be the long description. func test1() {} // This is the short description of 'test'. // As the previous first line ends with '.', this is another paragraph, so this should be // the long description. No need for an empty line before. func test() {}

For constants or enum values, the document comment is the one declared at the end of the line.

const A = 0 // This is the documentation comment of constant 'A' enum Color { Red // This is the documentation comment of enum value 'Red' Blue // This is the documentation comment of enum value 'Blue' }

References

You can create a reference to something in the current module with [[name]] or [[name1.name2 etc.]]

// This is a function with a 'value' parameter. func one(value: s32) {} // This is a reference to [[one]] func two() {}

NoDoc

You can use the #[Swag.NoDoc] attribute to prevent a certain element from showing up in the documentation.

// The function 'three' will be ignored when generating the documentation. #[Swag.NoDoc] func three() {}

Examples

In Swag.DocKind.Examples mode, documentation is generated systematically, with each file representing a chapter or subchapter. The following guidelines outline the structure and formatting required for effective documentation creation.

File Naming Convention

File names must adhere to the format DDD_DDD_name, where each D represents a digit. This naming convention facilitates the hierarchical organization of the documentation.

Main Titles

Files with names formatted as 100_000_my_title.swg will generate a main heading (<h1>My Title</h1>).

Subtitles

Files named 101_001_my_sub_title.swg or 102_002_my_sub_title.swg will generate subheadings (<h2>My Sub Title</h2>) under the corresponding main title.

Multiple Main Titles

For separate sections, such as 110_000_my_other_title.swg, another main heading (<h1>My Other Title</h1>) will be generated.

File Type Flexibility

You can mix .swg files with .md files. For example, 111_000_my_other_title.md will seamlessly integrate Markdown files into the documentation structure.

Comment Format for Documentation

To include comments in your code that should be interpreted as part of the documentation (as opposed to standard Swag comments), use the following syntax:

/** This is a valid documentation comment. The comment must start with /** and end with */, each on a separate line. */

These comments will be processed and included in the generated documentation, ensuring that inline comments are properly formatted and contribute to the final output.

Source of Documentation

The documentation you are reading is generated from the std/reference/language module. This directory contains examples and files structured according to these guidelines, showcasing how to effectively create and manage documentation in Swag.DocKind.Examples mode.

Pages

In Swag.DocKind.Pages mode, each file generates an individual webpage, with the page name matching the file name. Aside from this distinction, the behavior is consistent with that of Swag.DocKind.Examples mode.

File Naming and Page Generation

Each file in this mode generates a separate webpage. The page name will directly correspond to the file name.

Use Case

Swag.DocKind.Pages mode is particularly useful for generating individual web pages, as demonstrated in the example directory. This mode is ideal for creating standalone pages that can be linked together or accessed independently, making it a versatile option for web-based documentation projects.

Generated on 08-09-2024 with swag 0.40.0