SWAG

Swag Language Reference

Introduction

The Test Module

The swag-lang/swag/bin/reference/language module provides a foundational introduction to the syntax and essential features of the Swag programming language. It is designed to offer insight into core language elements, separate from the more extensive Swag standard modules (Std).

Scope

This documentation is auto-generated directly from the module’s source code and serves as a basic guide to Swag’s syntax and functionality. Advanced topics such as dynamic arrays, dynamic strings, and hash maps are covered in the Std.Core module documentation, which you should reference for more sophisticated implementations beyond the examples covered here.

Running the Test Module

The reference/language module is implemented as a test module to demonstrate the language’s core features in action. You can run the module by executing the following commands from the terminal:

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

These commands execute all test modules within the specified workspace, including reference/language. If you are already working within the workspace directory, the --workspace flag (or shorthand -w) can be omitted.

Compiling and Running a Specific Test Module

To compile and execute a specific test module, such as test_language, use the --module (or -m) flag in conjunction with the swag test command:

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

This will target and run only the specified module, enabling you to focus on individual components for testing and development purposes.


Key Features of the reference/language Module:

  1. Core Syntax: Covers the fundamental elements of the Swag programming language, including variables, control structures, functions, and basic data types.
  2. Test-Driven: All examples and modules are designed to be executable as tests, allowing developers to verify their understanding of core Swag concepts in real time.
  3. Auto-Generated Documentation: This guide is generated directly from the source code, ensuring accuracy and up-to-date information on Swag's core functionality.

For further exploration of Swag’s advanced features, such as memory management or concurrency, consult the Std.Core documentation.

The Basics

Swag Language Overview

Swag is a modern and flexible programming language designed for ease of use, combining a strong type system with an intuitive syntax. This section introduces the fundamental concepts of Swag through concise examples, covering variables, functions, control flow, and more.

Constants and Variables

Use const to define constants, var for mutable variables, and let for immutable variables (single-assignment, set once at runtime).

  • const values are immutable and must be initialized at declaration.
  • var values can be reassigned.
  • let values are immutable after initialization.
#test { const Pi = 3.14159 // Constant value, cannot be changed var radius = 10 // Mutable variable let height = 100 // Immutable variable (single assignment) }

Mutability and Reassignment

Variables declared with var can be reassigned as needed.

#test { var counter = 0 counter = 5 // Reassigning the value of 'counter' }

Constants (const) and immutable variables (let) cannot be reassigned.

#test { const MaxCount = 10 // MaxCount = 12 // Error: Cannot assign to constant let width = 50 // width = 60 // Error: Cannot reassign an immutable variable }

Type Inference

Swag infers types automatically if they can be deduced from assigned values.

#test { let greeting = "Hello, Swag!" // Inferred as 'string' var age = 25 // Inferred as 's32' }

You can also explicitly declare a type when desired.

#test { let name: string = "Alice" var score: s32 = 100 }

Constants and Immutability

let and const emphasize immutability. let allows runtime initialization, while const is typically resolved at compile-time.

Printing Values

Use the @print intrinsic to display output in the console.

const CanPrint = false // Must be 'true' to show '@print' results func print(x: ...) { if CanPrint do @print(x) } #test { let name = "Swag" print("Welcome to ", name, " language!\n") }

This prints: 'Welcome to Swag language!'

Comments

Swag supports both single-line and multi-line comments.

#test { // This is a single-line comment /* This is a multi-line comment that spans multiple lines. */ }

Semicolons

Semicolons are optional at the end of statements. They’re only needed when placing multiple statements on the same line.

#test { let a = 5 let b = 10 print(a + b, "\n") // Outputs 15 }

Integers and Floating-Point Numbers

Swag supports both integer and floating-point numeric types.

#test { let maxHeight: u32 = 500 // Unsigned 32-bit integer let distance: f64 = 42.195 // Double-precision float let result = maxHeight + 50 print("New height: ", result, "\n") }

Strings

Strings are UTF-8 encoded and handle international text naturally.

#test { let message = "Hello, 世界!" print(message, "\n") }

Tuples

Tuples group multiple values of possibly different types. They are useful for returning multiple values or bundling related data.

#test { let person = {"Alice", 30, true} let (name, age, isActive) = person print(name, " is ", age, " years old. Active: ", isActive, "\n") // Access tuple elements by index: print(person.item0, " is ", person.item1, " years old.\n") }

Control Flow

Swag uses if, for, and switch for flow control. Curly braces {} are optional; you may use do blocks instead.

#test { let temperature = 30 if temperature > 25 do print("It's hot!\n") else do print("It's cool!\n") }

You can also use braces for control blocks.

#test { let temperature = 30 if temperature > 25 { print("It's hot!\n") } else { print("It's cool!\n") } }

Swag supports both for and while loops.

#test { // Iterate 10 times for 10 { print("Iteration: ", @index, "\n") } for var i: u32 = 0; i < 10; i += 2 { print("Iteration: ", i, "\n") } var countdown: u32 = 5 while countdown > 0 { print("T-minus ", countdown, "\n") countdown -= 1 } }

foreach Loop

foreach iterates over collections such as arrays.

#test { let numbers = [1, 2, 3, 4, 5] foreach num in numbers { print("Number: ", num, "\n") } }

Functions

Functions encapsulate reusable blocks of logic.

#test { func add(a: s32, b: s32)->s32 { return a + b } let result = add(10, 5) print("Result: ", result, "\n") // Outputs 15 }

Functions can also return multiple values via tuples.

#test { func splitName()->{ x: string, y: string } { return {"a", "b"} } let (firstName, lastName) = splitName() @assert(firstName == "a") @assert(lastName == "b") }

Error Handling

Swag uses throw, try, and catch for robust error management. Errors are treated as values, not exceptions.

Propagating Errors with throw and try

func divide(a: s32, b: s32)->s32 throw { if b == 0 { throw Swag.BaseError{"Division by zero!"} } return a / b } #test { let result = try divide(10, 2) print("Result: ", result, "\n") // Outputs 5 }

Handling Errors with catch

Use catch to handle errors locally. The intrinsic @err holds the error value.

#test { let result = catch divide(10, 0) if @err != null { print("Error: ", @err, "\n") } else { print("Result: ", result, "\n") } }

Dismissing Errors with trycatch

trycatch dismisses errors and continues execution, assigning a default value to the target variable.

func safeDivide(a: s32, b: s32)->s32 { return trycatch divide(a, b) } #test { let result = safeDivide(10, 0) print("Result: ", result, "\n") // Outputs 0 (default value) }

Hello Mad World

'Hello World' Examples

Let's dig deeper into the classic 'hello world' example. The simplest version requires no external dependencies, such as the Swag standard modules.

#main is the program entry point, a special compiler function (its name starts with #). It must be defined only once in a native executable.

@print is an intrinsic, a built-in function (its name starts with @). All intrinsics are part of the compiler runtime, which is bundled with the compiler.

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

Next, a version using the Core.Console.print function from the Std.Core module. The Std.Core module must be imported before use, but this example keeps things simple.

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

A #run block executes at compile time, allowing Swag to behave like a scripting language. In the following example, the famous message is printed by the compiler during compilation.

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

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

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

A version that generates code dynamically using meta programming.

using Core #main { const Msg = "Hello mad world!\n" // The result of a '#ast' block is a string compiled in place. // This is equivalent to calling 'Console.print(Msg)' directly. #ast { var sb = StrConv.StringBuilder{} sb.appendString("Console.print(Msg)") return sb.toString() } }

Finally, an even more elaborate example combining compile-time execution and meta programming.

using Core #main { // '#run' forces the call to 'mySillyFunction()' at compile time // even though it is not marked with '#[Swag.ConstExpr]' const Msg = #run mySillyFunction() Console.print(Msg) } // The attribute '#[Swag.Compiler]' tells Swag that this function is for // compile-time only, and 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() } // Use and return the constant created above return MyConst }

This entire sequence of meta-programming and compile-time evaluation ultimately produces the following equivalent runtime code:

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

Code Structure

Source Code Organization

Source Files and Workspaces

In Swag, all source files must use the .swg extension, except for 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 compile individual source files (except for .swgs scripts). Instead, source code is organized within a workspace that contains one or more modules. This modular approach promotes clean structure and efficient management of complex projects.

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

A module can either produce a dll (on Windows) or an executable. A single workspace may include multiple modules, typically comprising both the modules you develop (such as your main executable) and any external dependencies.

Generally, the entire workspace is compiled together, ensuring that all modules and dependencies are properly built, up to date, and fully integrated.

Global Declaration Order

Top-Level Declaration Order

The order of all top-level declarations in Swag does not matter. This means you can reference constants, variables, or functions before they are defined — either within the same file or across multiple files.

This flexibility is especially useful in large codebases, where logical flow or readability may benefit from organizing code independently of 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 yet declared or defined. 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 example, we demonstrate Swag's flexibility by calling the function functionDeclaredLater before it is defined. This behavior illustrates that Swag does not impose order restrictions on function declarations.

#run { // Call the function 'functionDeclaredLater' before it is declared. // Swag allows this because top-level declarations are order-independent. functionDeclaredLater() } // The function is declared here, after it has already been called. func functionDeclaredLater() {}

This flexibility also applies across multiple files. For example, you can call a function in one file and define it in another. Swag's global declaration model is intentionally non-restrictive, allowing you to structure your code in the way that best supports clarity and maintainability.

Comments

Comments

Swag supports both single-line and multi-line comments, providing flexibility for inline documentation and explanations within your code. This helps maintain clarity and readability, especially in larger or more 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' assigned the value '0'. const B = /* false */true // A constant named 'B' assigned the value 'true', with an inline // comment noting that 'false' was an alternative value considered.

Nested Comments

Swag also supports nested comments within multi-line comments. This can be particularly useful when temporarily disabling a block of code or when adding 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 or C++, there is no strict requirement to end statements with a semicolon (;). In Swag, a statement naturally terminates at the end of a line ('end of line'). This allows for cleaner, more concise syntax and reduces visual clutter, making the code easier to read and maintain.

#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 logic. @assert(x == 2) // Confirms that 'x' equals 2. Raises an error if it fails. @assert(y == x) // Confirms that 'y' equals 'x', which should also be 2. }

Optional Semicolons

While semicolons are optional, they can still be used if desired. In some cases, using semicolons may enhance code clarity or readability, particularly in 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 types inferred automatically. // Increment both variables by 1. x, y += 1 // Both 'x' and 'y' now have the value 1. }

Multiple Statements on a Single Line

Semicolons are especially useful when writing multiple statements on a single line. Although this can make code more compact, it is generally recommended to use it sparingly, as it may reduce readability in complex code.

#test { // Two variable declarations and initializations on a single line. var x = 0 var y = 0 // Increment both 'x' and 'y' on the same line. x += 1; y += 1 // Assert correctness of both variables. @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. Identifiers may 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

Some identifiers may begin with #, indicating a compiler directive or special function. These instructions have specific roles within the Swag environment and are evaluated at compile time.

Examples of compiler instructions:

#assert #run #main

Intrinsics

Identifiers that start with @ represent intrinsic functions. These functions are provided by the compiler and 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 is do 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 prevent conflicts with language features.

not

Basic Types

These are the fundamental data types provided by the language. They are reserved keywords and form the core building blocks for variables, constants, and function return types.

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

Compiler Instructions

Compiler instructions are prefixed with #. They are reserved for specific operations within the Swag compiler and control various aspects of compilation and code generation. User-defined identifiers cannot start with #, ensuring there are no naming conflicts.

#run #ast #test #init #drop #main #premain #message #dependencies #global #load #foreignlib #assert #print #error #warning #import #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 #me #curlocation #callerlocation #callerfunction #swagversion #swagrevision #swagbuildnum #swagos #code #type #up #alias0 #alias1 #alias2 // and more generally #aliasN #uniq0 #uniq1 #uniq2 // and more generally #mixinN

Intrinsic Values

Intrinsic values begin with @ and are provided by the compiler for introspection or internal state access.

@err @index @args @pinfos @bytecode @compiler @modules @gvtd @rtflags

Miscellaneous Intrinsics

Intrinsic functions are prefixed with @ and provide low-level operations directly supported by the compiler or hardware. They can be used at both compile time and runtime.

@kindof @countof @dataof @mkslice @mkstring @mkcallback @mkany @mkinterface @dbgalloc @sysalloc @stringcmp @typecmp @is @as @getcontext @tableof @assert @breakpoint @init @drop @postcopy @postmove @compilererror @compilerwarning @panic @print @setcontext

Intrinsics from libc

These intrinsic functions expose standard libc functionalities, allowing developers to perform common mathematical, memory, and string operations. They are also prefixed with @ to prevent 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 adjust their behavior. They give developers finer control over how code is interpreted or executed.

#prom #wrap #nodrop #move #moveraw #reverse #ref #constref #err #noerr #null

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 both positive and negative values within their respective ranges. Each type is designed for efficient 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) @assert(b == -2) @assert(c == -3) @assert(d == -4) @assert(#sizeof(a) == 1) // 'a' is an 's8' → 1 byte @assert(#sizeof(b) == 2) // 'b' is an 's16' → 2 bytes @assert(#sizeof(c) == 4) // 'c' is an 's32' → 4 bytes @assert(#sizeof(d) == 8) // 'd' is an 's64' → 8 bytes }

Unsigned Integers

Swag also supports unsigned integer types: u8, u16, u32, and u64. These types can only represent non-negative values, making them ideal for counting, indexing, and other operations 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) @assert(b == 2) @assert(c == 3) @assert(d == 4) @assert(#sizeof(a) == 1) // 'a' is a 'u8' → 1 byte @assert(#sizeof(b) == 2) // 'b' is a 'u16' → 2 bytes @assert(#sizeof(c) == 4) // 'c' is a 'u32' → 4 bytes @assert(#sizeof(d) == 8) // 'd' is a 'u64' → 8 bytes }

Floating-Point Types

Swag supports floating-point types f32 and f64. These represent single- and double-precision floating-point numbers, suitable for calculations requiring fractional values or higher precision.

#test { let a: f32 = 3.14 // 32-bit float (single precision) let b: f64 = 3.14159 // 64-bit float (double precision) @assert(a == 3.14) @assert(b == 3.14159) @assert(#sizeof(a) == 4) // 'a' is an 'f32' → 4 bytes @assert(#sizeof(b) == 8) // 'b' is an 'f64' → 8 bytes }

Boolean Type

The boolean type bool represents logical true or false values. In Swag, a boolean occupies 1 byte of memory.

#test { let a: bool = true let b: bool = false @assert(a == true) @assert(b == false) @assert(#sizeof(a) == 1) // 'bool' → 1 byte @assert(#sizeof(b) == 1) }

String Type

The string type represents text. Strings in Swag are UTF-8 encoded and stored as two 64-bit values: one for the data pointer and one for the length in bytes. This structure ensures efficient text manipulation and full Unicode compatibility.

#test { let a: string = "string 是" // UTF-8 encoded string @assert(a == "string 是") @assert(#sizeof(a) == 2 * #sizeof(*void)) // Pointer + length }

Rune Type

The rune type represents a 32-bit Unicode code point. It stores a single Unicode character and is ideal for per-character text operations across languages.

#test { let a: rune = '是' // Single Unicode code point @assert(a == '是') @assert(#sizeof(a) == 4) // 'rune' → 4 bytes (32 bits) }

Type Reflection

Swag supports type reflection at both compile time and runtime. This allows inspection and manipulation of types dynamically, enabling powerful and introspective programming techniques.

Further details about type reflection are explored in later sections.

Type Creation with #decltype

You can use #decltype to create a type based on an existing expression. This is useful for inferring or mirroring the type of another variable dynamically, improving reusability and reducing redundancy.

#test { let a = 0 // Type of 'a' inferred as 's32' let b: #decltype(a) = 1 // 'b' declared with same type as 'a' @assert(#typeof(a) == #typeof(b)) @assert(#typeof(a) == s32) #assert(#typeof(a) == #typeof(b)) #assert(#typeof(a) == s32) }

Number Literals

Number Representations

Integers can be written in multiple formats: decimal, hexadecimal, or binary. These representations allow you to express numbers in the format that best fits your use case or domain requirements.

#test { const a: u32 = 123456 // Decimal format const b: u32 = 0xFFFF // Hexadecimal, prefixed with '0x' (represents 65535) const c: u32 = 0b00001111 // Binary, prefixed with '0b' (represents 15) @assert(a == 123456) @assert(b == 65535) @assert(c == 15) }

Digit Separators

Numeric literals can use the underscore (_) as a digit separator for better readability. Separators do not affect the numeric value.

#test { const a: u32 = 123_456 // Decimal with separators const b: u32 = 0xF_F_F_F // Hexadecimal with separators const c: u32 = 0b0000_1111 // Binary with separators @assert(a == 123456) @assert(b == 65535) @assert(c == 15) }

Default Integer Types

In Swag, hexadecimal or binary literals default to type u32 if the value fits within 32 bits. If the literal exceeds 32 bits, it is automatically inferred as u64.

#test { const a = 0xFF // Fits in 32 bits → inferred as 'u32' #assert(#typeof(a) == u32) const b = 0xF_FFFFF_FFFFFF // Exceeds 32 bits → inferred as 'u64' #assert(#typeof(b) == u64) const c = 0b00000001 // Within 32 bits → inferred as 'u32' #assert(#typeof(c) == u32) const d = 0b00000001_00000001_00000001_00000001_00000001 // Exceeds 32 bits → 'u64' #assert(#typeof(d) == u64) }

Booleans

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

#test { const a = true #assert(a == true) const b, c = false #assert(b == false) #assert(c == false) }

Floating-Point Values

Floating-point literals use standard C-style notation. This makes them familiar and easy to read for developers coming from C or C++ backgrounds.

#test { let a = 1.5 @assert(a == 1.5) #assert(#typeof(a) == f32) // Default float type is 'f32' let b = 0.11 @assert(b == 0.11) let c = 15e2 @assert(c == 15e2) // Equivalent to 1500 let d = 15e+2 @assert(d == 15e2) let e = -1E-1 @assert(e == -0.1) }

Default Floating-Point Type

By default, floating-point literals are of type f32. This differs from C and C++, where floating-point literals default to double (f64).

#test { let a = 1.5 @assert(a == 1.5) #assert(#typeof(a) == f32) #assert(#typeof(a) != f64) }

Literal Suffix

You can specify the type of a literal explicitly by adding a suffix to it. 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' @assert(a == 1.5) @assert(a == 1.5'f64) #assert(#typeof(a) == f64) let b = 10'u8 // Declare 'b' as 'u8' @assert(b == 10) #assert(#typeof(b) == u8) let c = 1'u32 // Explicitly typed as 'u32' #assert(#typeof(c) == u32) }

String

UTF-8 Encoding

Swag uses UTF-8 encoding for strings, allowing representation of characters from virtually all languages and symbol sets.

String Comparison

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

#test { const a = "this is a Chinese character: 是" #assert(a == "this is a Chinese character: 是") const b = "these are some Cyrillic characters: ӜИ" #assert(b == "these are some Cyrillic characters: ӜИ") }

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 = '是' #assert(a == '是') #assert(#sizeof(a) == #sizeof(u32)) }
Warning

Direct indexing of a string to retrieve a rune is not supported, except for ASCII strings. Swag avoids the runtime overhead of UTF-8 decoding. The Std.Core module provides utilities for working with UTF-8 strings.

String Indexing

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

#test { const a = "this is a Chinese character: 是" // Retrieve the first byte ('t') const b = a[0] #assert(b == 't') #assert(#typeof(b) == #typeof(u8)) // Multibyte UTF-8 affects indexing const c = "是X是" #assert(c[1] != '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 #assert(a == "the devil's number is 666") const b = 666 let c = "the devil's number is not " ++ (b + 1) ++ "!" @assert(c == "the devil's number is not 667!") let d = "there are " ++ 4 ++ " apples in " ++ (2 * 2) ++ " baskets" @assert(d == "there are 4 apples in 4 baskets") }

Null Strings

A string can be null if it has not been initialized. This allows checking whether a string has been assigned a value.

#test { var a: string @assert(a == null) a = "not null" @assert(a != null) a = null @assert(a == null) }

Character Literals

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

Note

A character literal's quote must follow a symbol or space to avoid confusion with a type suffix.

#test { let char0 = 'a' let char1 = '我' let value = 5's32 // Type suffix example }

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 if the value fits within the target type.

#test { { let a: u8 = 'a' let b: u16 = 'a' let c: u32 = '我' let d: u64 = '我' let e: rune = '我' } { let a: s8 = 'a' let b: s16 = 'a' let c: s32 = '我' let d: s64 = '我' } }

Specifying Character Literal Types

You can specify a character literal’s type using a suffix to control its storage size.

#test { let a = '0''u8 @assert(a == 48) @assert(#typeof(a) == u8) let b = '1''u16 @assert(b == 49) @assert(#typeof(b) == u16) let c = '2''u32 @assert(c == 50) @assert(#typeof(c) == u32) let d = '3''u64 @assert(d == 51) @assert(#typeof(d) == u64) let e = '4''rune @assert(e == 52) @assert(#typeof(e) == rune) }

Escape Sequences

Swag supports escape sequences in strings and character literals to represent special characters.

An escape sequence starts with a backslash (') followed by a specific character.

#test { const a = "this is ASCII code 0x00: \0" const b = "this is ASCII code 0x07: \a" const c = "this is ASCII code 0x08: \b" const d = "this is ASCII code 0x09: \t" const e = "this is ASCII code 0x0A: \n" const f = "this is ASCII code 0x0B: \v" const g = "this is ASCII code 0x0C: \f" const h = "this is ASCII code 0x0D: \r" const i = "this is ASCII code 0x22: \"" const j = "this is ASCII code 0x27: \'" const k = "this is ASCII code 0x5C: \\" }

ASCII and Unicode Escape Sequences

Escape sequences can represent characters via their ASCII or Unicode values.

#test { { const a = "\x26" const b = "\u2626" const c = "\U00101234" } { const d = "\u2F46\u2F46" #assert(d == "⽆⽆") const e = '\u2F46' #assert(e == '⽆') } }

Raw Strings

A raw string is one where escape sequences and special characters are not processed. This is useful when working with many backslashes or special symbols.

A raw string is enclosed within # characters. Its content is taken literally.

#test { const a = #"\u2F46"# // Raw string containing a Unicode escape #assert(a != "⽆") #assert(a == #"\u2F46"#) }

Equivalent strings can be written using escape sequences or raw notation.

#test { const a = "\\hello \\world" const b = #"\hello \world"# #assert(a == b) }

Multiline Raw Strings

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

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

Multiline Strings

Multiline strings start and end with """. Unlike raw strings, escape sequences are still processed.

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

In multiline or raw strings, ending a line with ' prevents the following end-of-line from being included.

#test { const a = """\ 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") #assert(#stringof(X + 10) == "11") }

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

#test { const X = 1 #assert(#nameof(X) == "X") }

Constants

Constants with const

Using const means the value must be known by the compiler at compile time. The compiler embeds the constant’s value directly into the compiled code, removing any runtime memory usage for simple types like integers or strings.

In other words, the compiler replaces every occurrence of a constant with its literal value, leading to optimized and efficient code execution.

#test { // Immutable compile-time constants const a = 666 #assert(a == 666) const b: string = "string" #assert(b == "string") }

Constants with Complex Types

Swag also supports constants with complex data types, such as arrays and structures. When declared, the data is stored in the program's data segment, which occupies memory. The memory address of such constants can be accessed at runtime, allowing indirect manipulation through pointers.

Static Arrays

A static array has a fixed size. In this example, the constant array a contains three elements of type s32 (signed 32-bit integers).

#test { const a: [3] s32 = [0, 1, 2] let ptr = &a[0] @assert(ptr[0] == 0) @assert(ptr[2] == 2) // Compile-time verification of array contents #assert(a[0] == 0) #assert(a[1] == 1) #assert(a[2] == 2) }
Multidimensional Arrays

This example demonstrates a constant 4×4 matrix of 32-bit floating-point values (f32). Arrays will be explored in more detail later in this documentation.

#test { const M4x4: [4, 4] f32 = [ [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] }

Key Difference Between let and const

The key distinction between let and const lies in when their values are determined:

  • A const value is fixed and known at compile time.
  • A let value can be assigned at runtime, allowing computation before assignment.

Despite this difference, both let and const enforce immutability — once a value is assigned, it cannot be changed.

Variables

Variable Declaration

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

  • let — Declares an immutable variable. Once assigned, its value cannot change.
  • var — Declares a mutable variable. Its value can be modified after initialization.
#test { let a: u32 = 1 // Immutable variable @assert(a == 1) let b: string = "string" @assert(b == "string") var c: s32 = 42 // Mutable variable c += 1 @assert(c == 43) }

Multiple Variable Declarations

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

#test { let a, b: u32 = 123 @assert(a == 123) @assert(b == 123) }

Multiple variables of different types can also be declared on one line.

#test { let a: u32 = 12, b: f32 = 1.5 @assert(a == 12) @assert(b == 1.5) }

Default Initialization

If a variable is declared without an explicit value, it is automatically initialized with a default value. Variables in Swag are always initialized and never left undefined.

#test { var a: bool @assert(a == false) var b: string @assert(b == null) var c: f64 @assert(c == 0) }

Uninitialized Variables

To skip default initialization, you can assign undefined. This prevents automatic initialization but leaves the variable in an undefined state, which should be used cautiously.

#test { var a: bool = undefined var b: string = undefined }

Type Inference

Swag supports type inference, automatically determining a variable’s type based on its assigned value. This often eliminates the need for explicit type annotations.

#test { let a = 1.5 @assert(a == 1.5) #assert(#typeof(a) == f32) let b = "string" @assert(b == "string") #assert(#typeof(b) == string) let c = 1.5'f64 @assert(c == 1.5) #assert(#typeof(c) == f64) }

Type inference also applies when declaring multiple variables simultaneously.

#test { let a, b = true @assert(a == true) @assert(b == true) #assert(#typeof(a) == #typeof(true)) #assert(#typeof(b) == #typeof(a)) let c = 1.5, d = "string" @assert(c == 1.5) @assert(d == "string") #assert(#typeof(c) == f32) #assert(#typeof(d) == string) }

Special Variables

Swag provides attributes that modify how variables are stored or accessed.

Thread-Local Storage

Global variables marked with #[Swag.Tls] are stored in thread-local storage. Each thread has its own copy of the variable.

#[Swag.Tls] var G = 0 // Thread-local global variable
Global Variables

A local variable can be marked with #[Swag.Global] to make it global within its function’s scope. It behaves similarly to static variables in C/C++, retaining its value between function calls.

#test { func toto()->s32 { #[Swag.Global] var G1 = 0 G1 += 1 return G1 } @assert(toto() == 1) @assert(toto() == 2) @assert(toto() == 3) }
Compile-Time Variables

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

#[Swag.Compiler] var G2 = 0 #run { G2 += 5 // Executes at compile time }

Operators

Arithmetic Operators

Arithmetic operators perform basic mathematical operations such as addition, subtraction, multiplication, division, and modulus (remainder).

#test { var x: s32 = 10 // Addition x = x + 1 // Subtraction x = x - 1 // Multiplication x = x * 2 // Division x = x / 2 // Modulus x = x % 2 }

Bitwise Operators

Bitwise operators manipulate individual bits of integral types. They include bitwise AND, OR, XOR, and bit shifting.

#test { var x: s32 = 10 x = x ^ 2 // XOR x = x & 0b0000_0001's32 // AND x = x | cast(s32) 0b0001 // OR x = x << 1 // Shift left x = x >> 1 // Shift right }

Assignment Operators

Assignment operators perform an operation and immediately assign the result to the left operand, creating concise expressions.

#test { var x: s32 = 10 x += 1 x -= 1 x *= 2 x /= 2 x %= 2 x ^= 2 x |= 0b0001 x &= 0b0001 x <<= 1 x >>= 1 }

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 x = !x // Logical NOT y = ~y // Bitwise NOT z = -z // Negation @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 comparisons.

#test { { var a = false a = 1 == 1 ? true : false a = 1 != 1 ? true : false a = 1 <= 1 ? true : false a = 1 >= 1 ? true : false a = 1 < 1 ? true : false 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 and return a boolean result. Swag uses and and or instead of && and || from C/C++.

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

Ternary Operator

The ternary operator tests an expression and returns one of two values depending on whether the condition is true or false.

Syntax: A = Condition ? B : C

#test { let x = true ? 1 : 666 @assert(x == 1) let y = (x == 52) ? 1 : 666 @assert(y == 666) }

Spaceship Operator

The <=> operator returns -1, 0, or 1 depending on whether the left operand is less than, equal to, or greater than the right operand. The result 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 orelse operator returns the left-hand expression if it is not null; otherwise, it returns the right-hand expression.

#test { var a = "string1" let b = "string2" var c = a orelse b @assert(c == "string1") a = null c = a orelse b @assert(c == "string2") }

The orelse operator also works with numeric and basic types.

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

Type Promotion

Unlike C, Swag does not automatically promote small integer types (8-bit or 16-bit) to 32 bits. However, when operands differ in type, promotion occurs to match the larger or signed type.

#test { #assert(#typeof(0'u8 + 1'u8) == u8) #assert(#typeof(0'u8 + 1'u16) == u16) #assert(#typeof(0'u8 + 1'u32) == u32) #assert(#typeof(0'u8 + 1'u64) == u64) #assert(#typeof(0'u8 + 1's8) == s8) #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) }

To prevent overflow during small integer operations, you can apply the #prom modifier to promote operands to at least 32 bits before the operation.

#test { #assert(#typeof(255'u8 + #prom 1'u8) == u32) #assert(255'u8 + #prom 1'u8 == 256) }

Operator Precedence

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

When two operators share the same precedence level, expressions are evaluated from left to right.

#test { @assert(10 + 2 * 3 == 16) @assert((10 + 2) * 3 == 36) @assert((5 + 3 < 10 - 2) == false) @assert((false and false or true) == true) @assert((10 & 2 << 1) == 0) @assert(((10 & 2) << 1) == 4) }

Cast

Explicit Cast with cast

Explicit casting converts a value from one type to another using the syntax 'cast(type) value'. This transformation changes the type of value to the specified type.

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

Automatic Cast with cast()

A cast() without a type performs an automatic cast, allowing the compiler to infer the target type based on the left-hand side of the assignment.

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

Automatic casts can also be applied when passing function arguments.

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

Bit Cast

The #bit cast mode performs a bit-level reinterpretation of a value’s type without altering its underlying bit pattern. Source and destination types must have the same size.

#test { let x: f32 = 1.0 let y: u32 = cast #bit (u32) x @assert(y == 0x3f800000) // 1.0 in IEEE 754 format #assert(cast #bit (u32) 1.0 == 0x3f800000) #assert(cast #bit (f32) 0x3f800000 == 1.0) }

Bitcasting also allows reinterpreting an integer’s bit pattern as a floating-point value, or vice versa.

#test { let rawBits: u32 = 0x40490FDB let pi: f32 = cast #bit (f32) rawBits @assert(pi == 3.1415927) let backToBits: u32 = cast #bit (u32) pi @assert(backToBits == 0x40490FDB) }

Implicit Casts

Swag supports implicit casts, automatically converting between compatible types when no data loss or precision loss can occur. These conversions happen without requiring an explicit cast statement.

Implicit Cast Rules
  1. Widening conversions — Allowed when converting from smaller to larger

types, such as s8s16 or f32f64. 2. Sign preservation — Implicit signed/unsigned conversions occur only if the value fits in the destination type. 3. No narrowing conversions — Conversions that could lose precision or range require an explicit cast.

#test { let x: s16 = 1's8 let y: s32 = 1's16 let z: s64 = 1's32 let a: u16 = 255'u8 let b: u32 = 65535'u16 let c: u64 = 4294967295'u32 let d: f64 = 1.23'f32 }

Disallowed Implicit Casts

Swag forbids implicit casts that may lose data or precision. In such cases, an explicit cast is required. The #wrap or #unsafe cast modes can be used to acknowledge potential data loss.

#test { // Explicit cast from 's16' to 's8' required let z0: s16 = 256 let z1: s8 = cast #wrap (s8) z0 @assert(z1 == 0) // Explicit cast from 'u16' to 's16' required let u_val: u16 = 65535 let s_val: s16 = cast #wrap (s16) u_val @assert(s_val == -1) // Explicit cast from 'f64' to 'f32' required let large_float: f64 = 1.23456789012345611111 let smaller_float: f32 = cast(f32) large_float }

Alias

Type Alias

An alias creates a shorthand or alternative name for an existing type. This can improve readability, reduce repetition, and simplify the use of complex types. A type alias does not create a new type — it simply provides a new name for an existing one.

Basic Type Alias

Using alias, you can define a shorthand for any existing type. The alias can be used interchangeably with the original type throughout your code.

#test { enum RGB { R, G, B } @assert(RGB.R == 0) alias Color = RGB @assert(Color.G == 1) }

Aliasing Primitive Types

Aliases can be created for primitive types to make your code more expressive or domain-specific, improving clarity when a type has a specific contextual meaning.

#test { alias Float32 = f32 alias Float64 = f64 var x: Float32 = 1.0 var y: Float64 = 1.0 #assert(#typeof(Float32) == f32) #assert(#typeof(Float64) == f64) }

Strict Type Alias

When you need stronger type safety, you can use the #[Swag.Strict] attribute. This makes the alias a distinct type, disallowing implicit conversions between the alias and its base type. Explicit casting is still permitted.

#test { #[Swag.Strict] alias MyType = s32 #assert(#typeof(MyType) != s32) let x: MyType = cast(MyType) 0 let y: s32 = cast(s32) x }

Name Alias

The alias keyword can also create shortcuts for functions, variables, or namespaces. This helps simplify long names and improve code readability.

Function Name Alias

A function can be aliased to a shorter or more convenient name, which is especially useful for lengthy or descriptive function names.

#test { func thisIsABigFunctionName(x: s32) => x * x alias myFunc = thisIsABigFunctionName @assert(myFunc(4) == 16) }
Variable and Namespace Alias

You can alias variables or namespaces to shorter names for easier access, especially in large codebases with long identifiers.

#test { var myLongVariableName: s32 = 0 alias short = myLongVariableName short += 2 @assert(myLongVariableName == 2) }

Data Structures

Array

Static Arrays in Swag

Static arrays are fixed-size arrays where the size is known at compile time. Unlike dynamic arrays from the Std.Core module, they cannot grow or shrink during execution. Static arrays are ideal when size and memory usage are deterministic.

Declaring a Static Array

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

#test { var array: [2] s32 array[0] = 1 array[1] = 2 }

Array Size and Memory

The @countof intrinsic returns the number of elements in an array, while #sizeof returns the total memory size in bytes.

#test { var array: [2] s32 #assert(#typeof(array).count == 2) #assert(@countof(array) == 2) #assert(#sizeof(array) == 2 * #sizeof(s32)) }

Obtaining the Address of an Array

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

#test { var array: [2] s32 var ptr0 = @dataof(array) ptr0[0] = 1 var ptr1 = &array[0] ptr1[1] = 2 @assert(array[0] == 1) @assert(array[1] == 2) }

Array Literals

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

#test { var arr = [1, 2, 3, 4] #assert(@countof(arr) == 4) #assert(#typeof(arr) == #type [4] s32) }

Type Deduction in Arrays

Swag can deduce array size and element type from the initialization expression.

#test { var array: [?] s32 = [1, 2] @assert(array[0] == 1) @assert(array[1] == 2) #assert(@countof(array) == 2) 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 are automatically initialized with zero values (0 for numbers, null for strings, false for booleans, etc.) unless specified otherwise.

#test { var array: [2] s32 @assert(array[0] == 0) @assert(array[1] == 0) }

Preventing Default Initialization

You can skip default initialization by using undefined, which improves performance when the array will be manually initialized later.

#test { var array: [100] s32 = undefined }

Constant Arrays

Arrays initialized with compile-time values can be declared as const, making them immutable after declaration.

#test { const array = [1, 2, 3, 4] #assert(array[0] == 1) #assert(array[3] == 4) }

Type Inference from Array Literals

If no explicit type is specified, Swag infers the array’s type from the first element. All other elements are promoted to match that type.

#test { var arr = [1'f64, 2, 3, 4] #assert(@countof(arr) == 4) #assert(#typeof(arr) == #type [4] f64) @assert(arr[3] == 4.0) }

Multi-Dimensional Arrays

Swag supports multi-dimensional arrays using the syntax '[X, Y, Z...]', where each number represents a dimension.

#test { var array: [2, 2] s32 array[0, 0] = 1 array[0, 1] = 2 array[1, 0] = 3 array[1, 1] = 4 }

C/C++ Style Multi-Dimensional Arrays

You can also declare multi-dimensional arrays using nested array syntax, similar to C/C++.

#test { var array: [2] [2] s32 array[0, 0] = 1 array[0, 1] = 2 array[1, 0] = 3 array[1, 1] = 4 }

Array Size Deduction

Swag can infer the dimensions of arrays — including multi-dimensional ones — directly from the initialization expression.

#test { var array = [1, 2, 3, 4] var array1 = [[1, 2], [3, 4]] #assert(@countof(array) == 4) #assert(@countof(array1) == 2) }

Single Value Initialization

An entire array can be initialized with a single value. This feature applies to variables (not constants) and works for basic types such as integers, floats, strings, booleans, and runes.

#test { var arr: [2, 2] bool = true @assert(arr[0, 0] == true) @assert(arr[1, 1] == true) var arr1: [5, 10] string = "string" @assert(arr1[0, 0] == "string") @assert(arr1[4, 9] == "string") }

Slice

Slices in Swag

A slice provides a dynamic view over a contiguous block of memory. Unlike static arrays, slices can point to different memory regions or subsets of existing data at runtime.

A slice consists of a data pointer and a u64 count representing the number of elements. This allows efficient access to large datasets without copying memory.

#test { var a: [..] bool #assert(#sizeof(a) == #sizeof(*void) + #sizeof(u64)) }

Initializing Slices

Slices can be initialized directly with array literals. The slice will reference the array’s elements.

#test { var a: const [..] u32 = [10, 20, 30, 40, 50] @assert(@countof(a) == 5) @assert(a[0] == 10) @assert(a[4] == 50) a = [1, 2] @assert(@countof(a) == 2) @assert(a[0] == 1) @assert(a[1] == 2) }

Accessing Slice Data

The @dataof intrinsic retrieves the slice’s data address, and @countof returns the number of elements.

#test { var a: const [..] u32 = [10, 20, 30, 40, 50] let count = @countof(a) let addr = @dataof(a) @assert(count == 5) @assert(addr[0] == 10) @assert(addr[4] == 50) a = [1, 2] @assert(@countof(a) == 2) }

Creating Slices with @mkslice

The @mkslice intrinsic creates a slice from a pointer and an element count.

#test { var array: [4] u32 = [10, 20, 30, 40] let slice: [..] u32 = @mkslice(&array[0] + 2, 2) @assert(@countof(slice) == 2) @assert(slice[0] == 30) @assert(slice[1] == 40) slice[0] = 314 @assert(array[2] == 314) }

Slicing Strings

Strings can be sliced, but the result must be declared as const since 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

The .. operator can be used to create slices directly.

#test { let str = "string" let slice = str[1 to 3] @assert(slice == "tri") }

Inclusive and Exclusive Slicing

By default, the upper bound in a slice is inclusive. To exclude it, use until instead of to.

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

Slicing to the End

Omitting the upper bound creates a slice extending to the end of the sequence.

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

Slicing from the Start

Omitting the lower bound starts the slice from index 0.

#test { let str = "string" let slice = str[ to 2] @assert(slice == "str") let slice1 = str[ until 2] @assert(slice1 == "st") }

Slicing Arrays

Arrays can be sliced the same way as strings.

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

Slicing a Slice

A slice can be further sliced to produce another slice.

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

Transforming a Pointer into a Slice

A pointer can be transformed into a slice by specifying a range.

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

Tuple

Tuples in Swag

A tuple represents an anonymous structure (a struct literal) that can group multiple values of different types together without defining a named structure. Tuples are enclosed in curly braces {} and can mix any combination of data types.

#test { let tuple1 = {2, 2} let tuple2 = {"string", 2, true} }

Accessing Tuple Values

Tuple fields are automatically named itemX, where X is the zero-based index of the field.

#test { let tuple = {"string", 2, true} @assert(tuple.item0 == "string") @assert(tuple.item1 == 2) @assert(tuple.item2 == true) }

Named Fields in Tuples

Tuple fields can have explicit names. Named fields improve readability but can still be accessed using their default itemX identifiers.

#test { let tuple = {x: 1.0, y: 2.0} @assert(tuple.x == 1.0) @assert(tuple.item0 == 1.0) @assert(tuple.y == 2.0) @assert(tuple.item1 == 2.0) }

Automatic Field Naming

When creating a tuple from variables, Swag automatically uses the variable names as field names unless overridden.

#test { let x = 555 let y = 666 let t = {x, y} @assert(t.x == 555) @assert(t.item0 == t.x) @assert(t.y == 666) @assert(t.item1 == t.y) }

Tuple Assignment and Compatibility

Tuples can be assigned to each other if their field types match, even if field names differ.

#test { var x: { a: s32, b: s32 } var y: { c: s32, d: s32 } y = {1, 2} x = y @assert(x.a == 1) @assert(x.b == 2) #assert(#typeof(x) != #typeof(y)) }

Tuple Unpacking

Tuples can be unpacked into separate variables, allowing easy extraction of values.

#test { let tuple1 = {x: 1.0, y: 2.0} let (value0, value1) = tuple1 @assert(value0 == 1.0) @assert(value1 == 2.0) let tuple2 = {"name", true} let (name, value) = tuple2 @assert(name == "name") @assert(value == true) }

Ignoring Fields During Unpacking

Use ? as a placeholder to ignore specific tuple fields when unpacking.

#test { let tuple1 = {x: 1.0, y: 2.0} let (x, ?) = tuple1 @assert(x == 1.0) let (?, y) = tuple1 @assert(y == 2.0) }

Enum

Enums in Swag

Enums define a set of named constant values. Unlike C/C++, Swag allows enum values to end with ;, ,, or simply a new line.

#test { enum Values0 { A, B } enum Values1 { A, B } enum Values2 { A, B } enum Values3 { A, B } }

Enum Underlying Type

By default, enums use the s32 type for storage.

#test { enum Values { A, B } let type = #typeof(Values) @assert(type.rawType == s32) #assert(#typeof(Values) == Values) }

Custom Enum Underlying Type

A custom base type can be specified after the enum name.

#test { enum Values1: s64 { A B C } #assert(#typeof(Values1).rawType == s64) #assert(#typeof(Values1.A) == Values1) }

Default and Custom Enum Values

Without explicit values, enums start at 0 and increment by 1.

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

Custom values can be assigned manually.

#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

After a custom value, following values auto-increment.

#test { enum Value: u32 { A = 10 B C } #assert(Value.A == 10) #assert(Value.B == 11) #assert(Value.C == 12) }

Non-Integer Enum Values

Non-integer enums require explicit assignments.

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

Counting Enum Values

@countof returns the number of enum members.

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

Using using with Enums

using allows direct access to enum values without prefixing with the enum name.

#test { enum Value { A, B, C } using Value @assert(A == 0) @assert(B == 1) @assert(Value.C == 2) }

Enums as Flags

With #[Swag.EnumFlags], enum values represent bit flags. They should use unsigned integer types.

#test { #[Swag.EnumFlags] enum MyFlags: u8 { A, B, C, D } #assert(MyFlags.A == 0b00000001) #assert(MyFlags.B == 0b00000010) #assert(MyFlags.C == 0b00000100) let value = MyFlags.B | MyFlags.C @assert(value == 0b00000110) }

Enums with Arrays

Enums can store array constants.

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

Enums with Slices

Enums can also store slices.

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

Nested Enums

Enums can include other enums using using. Both must share the same base type.

enum BasicErrors { FailedToLoad, FailedToSave } enum MyErrors { using BasicErrors NotFound = 100 }

Accessing Nested Enums

Access nested values with their scoped names.

const MyError0 = MyErrors.BasicErrors.FailedToSave

Automatic Cast with Nested Enums

Nested enums can be automatically cast to their parent enum type.

#test { const E0: MyErrors = MyErrors.BasicErrors.FailedToLoad const E1: BasicErrors = BasicErrors.FailedToLoad func toto(err: MyErrors) { @assert(err == BasicErrors.FailedToLoad) } toto(E0) toto(E1) }

Specific Enum Attributes

#[Swag.EnumIndex] allows direct indexing with enum values.

#test { #[Swag.EnumIndex] enum MyIndex { First, Second, Third } const Array = [0, 1, 2] const Valu = Array[MyIndex.First] }

#[Swag.NoDuplicate] prevents duplicate values.

#test { #[Swag.NoDuplicate] enum MyEnum { Val0 = 0 } }

Enum Type Inference

Swag infers enum types automatically in assignments and expressions.

#test { enum Values { A, B } let x: Values = Values.A let y: Values = A @assert(x == y) }

Type Inference in switch

Enum types are inferred inside switch statements.

#test { enum Values { A, B } let x = Values.A switch x { case A: break case B: break } }

Simplified Enum Syntax

You can omit the enum name when the type is already known.

#test { enum Values { A, B } let x = Values.A @assert(x == .A) }

Simplified Enum Flags Syntax

This works with flags too.

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

Simplified Enum Syntax in Functions

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

Iterating Over Enum Values

You can iterate over enums using for or foreach.

#test { enum RGB { R, G, B } var cpt = 0 for idx in RGB do cpt += 1 @assert(cpt == 3) }

foreach offers a structured way to iterate and handle specific values.

#test { enum RGB { R, G, B } foreach val in RGB { switch val { case R: break case G: break case B: break default: @assert(false) } } }

Impl

Implementing Methods for Enums in Swag

Swag allows the use of the impl keyword with enums to define methods directly associated with them. This makes it possible to attach behavior to enum values, enhancing code organization and encapsulation.

Within these methods, the me keyword refers to the current enum instance, similar to this in other languages.

enum RGB { R // Represents Red G // Represents Green B // Represents Blue }

Defining Enum Methods with impl enum

The impl enum block defines methods that operate on enum values. This helps group functionality logically with the type it belongs to.

impl enum RGB { // Check if the current color is Red func isRed(me) => me == R // Check if the current color is Red or Blue func isRedOrBlue(me) => me == R or me == B } #test { // 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)) }

Simplifying Calls with using

The using keyword allows you to call enum methods without qualifying them with the enum name. This makes the code cleaner when the enum type is already in scope.

#test { using RGB @assert(isRedOrBlue(R)) @assert(isRedOrBlue(B)) }

Uniform Function Call Syntax (UFCS)

Swag supports Uniform Function Call Syntax (UFCS), meaning methods can be called directly on enum values. This provides a natural, object-oriented style of method invocation.

#test { using RGB // Call `isRedOrBlue` directly on the enum value @assert(R.isRedOrBlue()) // Verify that `G` is not recognized as red or blue @assert(!G.isRedOrBlue()) }

Union

The union Type in Swag

A union is a special kind of struct where all fields share the same memory location. Each field starts at offset 0, allowing multiple variables to overlap in memory.

This makes it possible to represent different data types using the same storage space, which is particularly useful for optimizing memory when only one field is used at a time.

#test { // Define a union with three overlapping f32 fields: x, y, and z union MyUnion { x, y, z: f32 } // Initialize the union, setting the value of the 'x' field let v = MyUnion{x: 666} // All fields share the same memory location — updating one affects all @assert(v.y == 666) @assert(v.z == 666) }

How Unions Work

In this example, the MyUnion type defines three fields (x, y, and z) that occupy the same memory space. Writing to one field overwrites the others, since they are all stored at the same offset.

This design is ideal for cases where different data types (or values) are stored alternately in the same memory area. Here, since all fields are of type f32, assigning a value to x automatically updates y and z, demonstrating the shared-memory behavior of unions.

Pointers

Single-Value Pointers

A pointer to a single element is declared using the * symbol. It represents the memory address of one instance of a given type.

Pointers can reference any type of data and are fundamental for efficient memory access and manipulation.

#test { var ptr1: *u8 // Pointer to a single 'u8' value var ptr2: **u8 // Pointer to another pointer to 'u8' }

Null Pointers

In Swag, a pointer can be null, meaning it does not refer to any valid memory location. Null pointers are often used to indicate uninitialized or intentionally empty references.

It is good practice to check for null pointers before dereferencing them to avoid runtime errors.

#test { var ptr1: *u8 @assert(ptr1 == null) // Uninitialized pointers are null by default }

Taking the Address of a Variable

The & operator returns the address of a variable, producing a pointer to it. This allows you to access or modify a variable indirectly via its memory address.

#test { var value = 1 let ptr = &value @assert(#typeof(ptr) == *s32) // The pointer type matches the variable's type }

Dereferencing a Pointer

To access the value stored at a pointer’s address, use the dref intrinsic. Dereferencing retrieves the data located at the memory address referenced by the pointer.

#test { var value = 42 let ptr = &value @assert(dref ptr == 42) // Access the value stored at the pointer }

Const Pointers

A const pointer is one whose target address cannot change once assigned. However, the data at that address may still be modified (unless declared const itself). Const pointers help guarantee that a pointer always refers to the same memory location.

#test { let str = "string" let ptr: const *u8 = @dataof(str) @assert(dref ptr == 's') // Access the first byte of the string }

Combining const with Pointers

Swag allows fine-grained control over constness in pointer declarations. You can define: - a pointer to const data, - a const pointer, or - a const pointer to const data.

This provides strong control over both pointer immutability and data mutability.

#test { var ptr: *const *u8 // Pointer to a const pointer var ptr1: const *const *u8 // Const pointer to a const pointer var ptr2: const **u8 // Const pointer to a mutable pointer }

Multi-Value Pointers

To enable pointer arithmetic or reference contiguous memory blocks, use [*] instead of *.

This type of pointer behaves like a view over multiple elements, allowing indexed access and pointer movement through memory.

#[Swag.Sanity(false)] #test { var ptr: [*] u8 // Pointer to a block of 'u8' values ptr = ptr - 1 // Move the pointer back by one element }

Pointer Arithmetic and Array Elements

When taking the address of an array element, the resulting pointer automatically supports pointer arithmetic since it refers to a sequence of elements.

#test { var arr: [4] s32 var ptr = &arr[1] // The pointer is treated as a multi-value pointer ptr = ptr - 1 #assert(#typeof(ptr) == [*] s32) }

Dereferencing with Indexing

When pointer arithmetic is available, you can dereference a pointer using indexes, just like accessing elements in an array.

This is particularly useful when traversing arrays or buffers via pointers.

#test { var arr = [1, 2, 3, 4] let ptr = &arr[0] @assert(#typeof(ptr) == [*] s32) let value1 = ptr[0] @assert(value1 == 1) #assert(#typeof(value1) == s32) let value2 = ptr[1] @assert(value2 == 2) #assert(#typeof(value2) == s32) let value = dref ptr @assert(value == 1) #assert(#typeof(value) == s32) }

References

References in Swag

Swag supports references, which behave like pointers but act syntactically like values. References offer a safer and more convenient way to work with memory addresses, eliminating the need for explicit pointer dereferencing in most cases.

#test { var x = 42 // Declare a constant reference to 'x'. // Unlike C++, you must explicitly take the address of 'x' to create the reference. let myRef: const &s32 = &x // References behave like aliases — no explicit dereferencing is needed. @assert(myRef == 42) }

Assigning Through References

Assigning a value to a reference (after initialization) updates the referenced variable, not the reference itself.

#test { var x = 42 var myRef: &s32 = &x @assert(myRef == 42) // Change the value of 'x' through the reference myRef = 66 @assert(myRef == 66) @assert(x == 66) // 'x' is updated as well }

Reassigning References

Unlike in C++, Swag allows reassigning a reference to point to a different variable. Use the #ref modifier to perform this operation safely.

#test { var x = 1 var y = 1000 var myRef: &s32 = &x @assert(myRef == 1) // Reassign the reference to point to 'y' instead of 'x' myRef = #ref &y @assert(myRef == 1000) }

Passing References to Functions

Normally, you take the address of a variable to create a reference. However, when passing a const reference to a function, Swag automatically takes the address — even for literals.

#test { // Passing a literal directly is allowed for const references toto(4) } func toto(x: const &s32) { @assert(x == 4) // A reference is still an address internally let ptr = &x @assert(dref ptr == 4) }

Using References with Structs

References are especially useful when working with structs. They allow you to pass entire structures or tuples directly, including literal expressions.

// Our first simple struct struct MyStruct { x: s32, y: s32 } #test { titi0({1, 2}) titi1({3, 4}) titi2({5, 6}) } func titi0(param: const &{ x: s32, y: s32 }) { // Tuples can be accessed by item index @assert(param.item0 == 1) @assert(param.item1 == 2) }

Equivalent Reference Passing

Declaring a tuple type or a struct type as a function parameter is equivalent to passing a constant reference.

func titi1(param: { x: s32, y: s32 }) { @assert(param.x == 3) @assert(param.y == 4) } func titi2(param: MyStruct) { @assert(param.x == 5) @assert(param.y == 6) }

Any

The any Type in Swag

any is a dynamically typed reference that can point to a value of any concrete type.

Warning

any is not a variant. It holds a reference to an existing value plus its runtime type info.

#test { var a: any // Store an s32 literal value in the 'any' a = 6 @assert(cast(s32) a == 6) // Now store a string a = "string" @assert(cast(string) a == "string") // Now store a bool a = true @assert(cast(bool) a == true) }

Working with any and Pointers

Use @dataof to access a pointer to the underlying value stored inside any.

#test { let a: any = 6 let ptr = cast(const *s32) @dataof(a) @assert(dref ptr == 6) }

Type Information and any

#typeof on an any yields any (the reference type). Use @kindof to get the concrete runtime type of the referenced value.

#test { var a: any = "string" #assert(#typeof(a) == any) @assert(@kindof(a) == string) a = true @assert(@kindof(a) == bool) }

Retrieving Values from any

You can retrieve the stored value directly or as a constant reference.

#test { let a: any = 42 #assert(#typeof(a) == any) @assert(@kindof(a) == 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 heterogeneous arrays where each element holds a different type.

#test { var array: [?] any = [true, 2, 3.0, "4"] @assert(@kindof(array[0]) == bool) @assert(@kindof(array[1]) == s32) @assert(@kindof(array[2]) == f32) @assert(@kindof(array[3]) == 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 null, similar to pointers and other nullable types.

#test { var x: any @assert(x == null) x = 6 @assert(x != null) @assert(cast(s32) x == 6) x = "string" @assert(x != null) @assert(cast(string) x == "string") x = null @assert(x == null) }

Type Checking with any

An any can be compared to a type using == or !=. This uses @kindof internally to compare the stored value's type.

#test { var x: any @assert(x == null) x = 6 @assert(x == s32) @assert(x != bool) x = "string" @assert(x != s32) @assert(x == string) struct A {} x = A{} @assert(x == 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 omit them, you must use do. This rule also applies to while and for loops.

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

#test { var a = 0 if a == 1 do @assert(false) if (a == 1) do @assert(false) if a == 0 { @assert(true) } // The 'else' keyword works as in most languages. // When omitting braces, 'do' is mandatory after the condition. if a == 0 do a += 1 else do a += 2 @assert(a == 1) // 'elif' functions like 'else if' in other languages. if a == 1 do a += 1 else do if a == 2 do @assert(false) elif a == 3 do @assert(false) elif a == 4 do @assert(false) // Logical expressions work as expected with 'and' and 'or'. if a == 0 and a == 1 do @assert(false) if a == 0 or a == 1 do @assert(false) if a == 1 or a == 2 do @assert(true) }

Variable Declaration in if

You can declare and test a variable directly in an if statement. When doing so, the use of var, let, or const is mandatory.

The declared variable is converted to a boolean for the condition: non-zero (or non-null) values are considered true.

#test { // Declare and test 'a' in the same line. // Since 'a' is 0, the condition is false and the block won’t execute. if let a = 0 { @assert(false) } // Redeclare 'a' as a constant in another scope. // Since 'a' is 1, the block will execute. if const a = 1 do @assert(a == 1) else do @assert(false) if let a = 1 do @assert(a == 1) else do @assert(false) }

Adding Conditions with where

When an if statement includes a variable declaration, you can refine the test with a where clause. The where condition is only evaluated if the variable test passes.

#test { func retSomething()->string => "string" func retNothing()->#null string => null // The 'where' clause runs only if 'str' is not null. if let str = retSomething() where str[0] == 's' do @assert(true) else do @assert(false) // Since 'retNothing()' returns null, the 'where' clause is skipped. if let str = retNothing() where str[0] == 's' do @assert(false) else do @assert(true) }

For

Introduction to for

The for construct in Swag enables iteration, allowing a block of code to execute repeatedly. This guide explores its features: basic usage, indexing, naming, reverse loops, early exits, range iteration, and advanced filtering using the where clause.

Basic Usage

The for expression specifies the number of iterations and is evaluated once before the loop starts. This expression must yield a positive integer.

#test { var cpt = 0 for 10 do cpt += 1 @assert(cpt == 10) }

Using @index

Within a for, the compiler provides the built-in @index, representing the current iteration (starting from 0).

#test { var cpt = 0'u64 for 5 { cpt += @index } @assert(cpt == 0 + 1 + 2 + 3 + 4) }

Naming the Loop Index

You can assign a custom name to the loop index for clarity, while @index remains accessible.

#test { var cpt = 0 var cpt1 = 0 for i in 5 { cpt += i cpt1 += @index } @assert(cpt == 0 + 1 + 2 + 3 + 4) @assert(cpt1 == cpt) }

Looping Over Arrays and Slices

The for construct can iterate over any type supporting @countof, such as arrays, slices, or strings.

#test { var arr = [10, 20, 30, 40] #assert(@countof(arr) == 4) var cpt = 0 for arr do cpt += arr[@index] @assert(cpt == 10 + 20 + 30 + 40) }
Warning

When iterating over strings, for loops over bytes, not runes. Use Std.Core for rune-based iteration.

#test { var cpt = 0 for "⻘" do cpt += 1 @assert(cpt == 3) }

Reverse Looping

To iterate in reverse order, add the #reverse modifier.

#test { var cpt = 0 for #reverse 3 { if cpt == 0 do @assert(@index == 2) elif cpt == 1 do @assert(@index == 1) elif cpt == 2 do @assert(@index == 0) cpt += 1 } }

break and continue

The break and continue keywords control loop flow. break exits the loop, while continue skips to the next iteration.

Exiting Early with break

break stops the loop before completing all iterations.

#test { var cpt = 0 for x in 10 { if x == 5 do break cpt += 1 } @assert(cpt == 5) }

Skipping Iterations with continue

continue skips the current iteration and proceeds to the next.

#test { var cpt = 0 for x in 10 { if x == 5 do continue cpt += 1 } @assert(cpt == 9) }

Ranges

The for loop supports signed integer ranges, offering flexible intervals.

Looping Over a Range with to

to defines an inclusive range loop.

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 { count += 1 sum += i } @assert(sum == 0) @assert(count == 3) }

Excluding the Last Value with until

until defines a range that excludes the end value.

#test { var cpt = 0 for i in 1 until 3 { cpt += i } @assert(cpt == 1 + 2) }

Reverse Range Looping

Use #reverse to iterate a range in reverse order.

#test { for #reverse 0 to 5 { } for #reverse -1 to 1 { } for #reverse -2 until 2 { } }

Infinite Loop

A for without an expression creates an infinite loop, similar to while true {}. Use break to exit.

#test { for { if @index == 4 do break } }

Using where Clause

The where clause filters iterations based on conditions.

Basic where Clause

Attach where to apply a filter to the loop index or element. Only iterations meeting the condition are executed.

#test { var result = 0 for i in 10 where i % 2 == 0 { result += i } @assert(result == 0 + 2 + 4 + 6 + 8) }

where with Arrays

Use where to filter array elements during iteration.

#test { var arr = [10, 21, 30, 41, 50] var sumOfEvens = 0 for i in arr where arr[i] % 2 == 0 { sumOfEvens += arr[i] } @assert(sumOfEvens == 10 + 30 + 50) }

Complex Conditions with where

The where clause supports multiple logical conditions for advanced filtering.

#test { var arr = [10, 15, 20, 25, 30, 35] var filteredSum = 0 for i in arr where arr[i] % 2 == 0 and arr[i] > 15 { filteredSum += arr[i] } @assert(filteredSum == 20 + 30) } #test { var arr = [10, 25, 30, 45, 50, 65] var complexSum = 0 for i in arr where arr[i] % 2 == 0 or arr[i] > 40 { complexSum += arr[i] } @assert(complexSum == 10 + 30 + 45 + 50 + 65) }

where with Ranges

The where clause can also be applied to ranges.

#test { var sumOfPositiveEvens = 0 for i in -5 to 5 where i > 0 and i % 2 == 0 { sumOfPositiveEvens += i } @assert(sumOfPositiveEvens == 2 + 4) }

Combining #reverse and where

You can combine #reverse with where for reverse conditional iteration.

#test { var arr = [10, 20, 30, 40, 50] var reversedSum = 0 for #reverse i in arr where arr[i] % 2 == 0 { reversedSum += arr[i] } @assert(reversedSum == 50 + 40 + 30 + 20 + 10) }

C-like for

Swag also supports a C-style for loop with initialization, condition, and increment sections.

#test { var cpt = 0 // Standard syntax for var i = 0; i < 10; i += 1 do cpt += 1 @assert(cpt == 10) // Alternative syntax with newlines for var i = 0; i < 10; i += 1 { cpt += 1 } @assert(cpt == 20) }

Accessing Loop Index with @index

In all for variants, @index provides the current iteration index.

#test { var cpt = 0'u64 for var i: u32 = 10; i < 15; i += 1 do cpt += @index @assert(cpt == 0 + 1 + 2 + 3 + 4) var cpt1 = 0'u64 for var i = 10; i < 15; i += 1 do cpt1 += @index @assert(cpt1 == 0 + 1 + 2 + 3 + 4) }

Using break and continue in for Loops

break exits the loop early, and continue skips to the next iteration.

#test { var sum = 0 for var i = 0; i < 10; i += 1 { if i == 5 do break sum += i } @assert(sum == 0 + 1 + 2 + 3 + 4) sum = 0 for var i = 0; i < 10; i += 1 { if i % 2 == 0 do continue sum += i } @assert(sum == 1 + 3 + 5 + 7 + 9) }

Nested for Loops

Swag supports nested loops. In nested contexts, @index refers to the current innermost loop.

#test { var result = 0'u64 for var i = 0; i < 5; i += 1 { for var j = 0; j < 5; j += 1 { result += @index } } @assert(result == 10 * 5) }

Iterating Over Arrays with for

You can also use for to iterate over arrays and collections. (Although foreach is generally preferred.)

#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) }

Foreach

Introduction to foreach

The foreach statement iterates over all elements of a collection (arrays, slices, strings, and even structs), providing a streamlined and efficient way to process each item.

#test { foreach value in "ABC" { let a = @index switch a { case 0: @assert(value == 'A') case 1: @assert(value == 'B') case 2: @assert(value == 'C') } } }

Naming the Value and Index

You can explicitly name both the element value and the loop index to improve readability, especially in nested loops or with complex data structures.

#test { foreach value, index in "ABC" { let a = index switch a { case 0: @assert(value == 'A') case 1: @assert(value == 'B') case 2: @assert(value == 'C') } } }

Using Default Aliases

If you do not name the element or index, you can access them with the default aliases: #alias0 for the element and #alias1 for the index.

#test { foreach "ABC" { let a = #alias1 @assert(a == @index) switch a { case 0: @assert(#alias0 == 'A') case 1: @assert(#alias0 == 'B') case 2: @assert(#alias0 == 'C') } } }

Reverse Order with #reverse

Iterate from the last element to the first by adding the #reverse modifier. @index still reflects the loop index within the original collection.

#test { var cpt = 0 foreach #reverse value in "ABC" { switch cpt { case 0: @assert(value == 'C') @assert(@index == 2) case 1: @assert(value == 'B') @assert(@index == 1) case 2: @assert(value == 'A') @assert(@index == 0) } cpt += 1 } }

Visiting Arrays and Slices

Use foreach to traverse arrays or slices and process each element.

#test { var array = [10, 20, 30] var result = 0 foreach it in array do result += it @assert(result == 10 + 20 + 30) }

Multi-dimensional Arrays

foreach works with multi-dimensional arrays and visits each element in row-major order.

#test { var array: [2, 2] s32 = [[10, 20], [30, 40]] var result = 0 foreach it in array do result += it @assert(result == 10 + 20 + 30 + 40) }

Modifying Elements with &

Prefix the element name with & to visit elements by address and modify them in place.

#test { var array: [2, 2] s32 = [[1, 2], [3, 4]] var result = 0 foreach &it in array { result += dref it dref it = 555 } @assert(result == 1 + 2 + 3 + 4) @assert(array[0, 0] == 555) @assert(array[0, 1] == 555) @assert(array[1, 0] == 555) @assert(array[1, 1] == 555) }

Filtering with where

Add a where clause to process only elements that satisfy a condition. This avoids explicit branching inside the loop.

#test { var array: [?] s32 = [1, 2, 3, 4] var result = 0 foreach value in array where value & 1 == 0 do result += value @assert(result == 6) // Equivalent with an 'if' guard: result = 0 foreach value in array do if value & 1 == 0 do result += value @assert(result == 6) // Equivalent using 'continue' to skip odd values: result = 0 foreach value in array { if (value & 1) != 0 do continue result += value } @assert(result == 6) }

While

Introduction to while Loops

A while loop repeatedly executes a block of code as long as its condition evaluates to true. Once the condition becomes false, the loop terminates.

#test { var i = 0 while i < 10 do i += 1 @assert(i == 10) }

Breaking Out of a while Loop

The break statement allows an early exit from a while loop before the condition becomes false. This is useful when you need to stop the loop based on a specific condition.

#test { var i = 0 while i < 10 { if i == 5 do break i += 1 } @assert(i == 5) }

Skipping Iterations with continue

The continue statement skips the current iteration and jumps to the next one. It is useful for ignoring specific cases within the loop.

#test { var sum = 0 var i = 0 while i < 10 { i += 1 if i % 2 == 0 do continue sum += i } @assert(sum == 25) }

Nested while Loops

A while loop can contain another while loop. break and continue only affect the loop in which they are directly used.

#test { var i = 0 var j = 0 var count = 0 while i < 3 { j = 0 while j < 3 { if j == 2 do break count += 1 j += 1 } i += 1 } @assert(count == 6) }

Using while with Complex Conditions

The condition in a while loop can include logical operators such as and and or for more complex and controlled iteration logic.

#test { var a = 0 var b = 1 var iterations = 0 while a < 100 and b < 200 { a += 10 b += 20 iterations += 1 } @assert(a == 100) @assert(b == 201) @assert(iterations == 10) }

Switch

Introduction to switch in Swag

The switch statement in Swag behaves similarly to C/C++, with one key difference: Swag does not require break at the end of each case. Unintentional fallthroughs are prevented by default, ensuring each case is isolated unless explicitly linked with fallthrough.

#test { let value = 6 switch value { case 0: @assert(false) case 5: @assert(false) case 6: @assert(true) default: @assert(false) } let ch = 'A''rune switch ch { case 'B': @assert(false) case 'A': break } }

Multiple Values in a case

A case can match multiple values, simplifying logic when several values share the same behavior.

#test { let value = 6 switch value { case 2, 4, 6: @assert(true) default: @assert(false) } // Alternatively, list each value on its own line for clarity. switch value { case 2, 4, 6: @assert(true) default: @assert(false) } }

Using switch with Various Types

switch supports any type that implements ==, including strings and user-defined comparable types.

#test { let value = "myString" switch value { case "myString": @assert(true) case "otherString": @assert(false) default: @assert(false) } }

Intentional Fallthrough with fallthrough

Use fallthrough to intentionally continue execution into the next case.

#test { let value = 6 switch value { case 6: fallthrough case 7: @assert(value == 6) default: @assert(true) } }

Exiting a case Early with break

Use break to exit a case early when a certain condition is met.

#test { let value = 6 switch value { case 6: if value == 6 do break @assert(false) default: @assert(false) } }

Handling Empty Cases with break

A case cannot be left empty. Use break explicitly when no action is required.

#test { let value = 6 switch value { case 5: @assert(false) case 6: break default: @assert(false) } }

Variable and Expression Cases

switch cases can use variables and expressions, evaluated dynamically at runtime.

#test { let test = 2 let a = 0 let b = 1 switch test { case a: @assert(false) case b: @assert(false) case b + 1: @assert(true) } }

The Swag.Complete Attribute

Annotate a switch with #[Swag.Complete] when matching enums to enforce exhaustive case handling.

#test { enum Color { Red, Green, Blue } let color = Color.Red #[Swag.Complete] switch color { case Color.Red: break case Color.Green: @assert(false) case Color.Blue: @assert(false) } }

Matching Ranges in a switch Statement

Swag supports matching a range of values in case conditions for concise range-based logic.

#test { var success = false let x = 6 switch x { case 0 to 5: @assert(false) case 6 to 15: success = true } @assert(success) }

Overlapping Ranges

When ranges overlap, the first matching range is executed; later overlaps are ignored.

#test { var success = false let x = 6 switch x { case 0 to 10: success = true case 5 until 15: @assert(false) } @assert(success) }

Using the where Clause in switch

Add a where clause to a case to refine matching conditions based on additional logic.

#test { let x = 6 let y = 10 switch x { case 6 where y == 9: @assert(false) case 6 where y == 10: @assert(true) default: @assert(false) } }

Using where with default

A where clause can also modify the default case for conditional fallbacks.

#test { let x = 7 let y = 10 switch x { case 6 where y == 10: @assert(false) case 7 where y == 9: @assert(false) default where y == 10: break default: @assert(false) } }

Switching on Type with any or interface

When switching on any or interface types, cases match based on the underlying runtime type.

#test { let x: any = "value" switch x { case string: break default: @assert(false) } }

Switch Statement with Type Guard and Variable Binding

You can bind a matched type or value to a variable using as, allowing direct access inside the case block. You may also use where for conditional refinement.

Example 1: Simple Type Binding

#test { let x: any = "value" switch x { case string as str: @assert(str == "value") break default: @assert(false) } }

Example 2: Type Binding with where Clause

#test { let x: any = "value" switch x { case string as str where str == "value": @assert(str == "value") break case string as str where str == "not_a_value": @assert(str == "not_a_value") break default: @assert(false) } }

Switch Without an Expression

A switch without an expression behaves like an if/elif chain. Each case is evaluated in order, executing the first one that evaluates to true.

#test { let value = 6 let value1 = "true" switch { case value == 6 or value > 10: @assert(true) fallthrough case value1 == "true": @assert(true) default: @assert(false) } }

Break

Introduction to break in Swag

The break statement exits the nearest enclosing control structure. It works with for, foreach, while, and switch. Use it to stop a loop early or to leave a switch case.

#test { // Immediately exits the loop body for 10 do break // Exits after the first iteration for var i = 0; i < 10; i += 1 do break // Not reached because the condition is false while false do break }

Default Behavior

By default, break exits only the innermost loop or control structure. Outer structures continue to run.

#test { var cpt = 0 for 10 { for 10 { // Exits only the inner loop break } // Outer loop continues cpt += 1 } @assert(cpt == 10) }

Named Scopes with #scope

Define a named scope with #scope(Name). 'break to Name' exits that scope from anywhere inside it.

#test { var cpt = 0 #scope(BigScope) { for 10 { cpt += 1 break to BigScope // Leave the whole 'BigScope' } @assert(false) // Unreachable } @assert(cpt == 1) }

Using continue with Named Scopes

Within a scope, continue restarts execution from the beginning of that scope. Combine a terminating condition with a plain break to end the scope loop.

#test { var cpt = 0 #scope(Loop) { cpt += 1 if cpt == 5 do break // End the 'Loop' scope continue // Jump back to the start of 'Loop' } @assert(cpt == 5) }

Unnamed Scopes

Scopes can be unnamed. break exits the current (unnamed) scope immediately. This can simplify multi-branch flows.

#test { let cpt = 0 #scope { if cpt == 1 { @assert(cpt == 1) break } if cpt == 2 { @assert(cpt == 2) break } if cpt == 3 { @assert(cpt == 3) break } } }

Scopes with Simple Statements

A scope label can precede a simple statement. 'break to Label' exits to just after that labeled statement.

#test { #scope(Up) for 10 { for 10 { if @index == 5 do break to Up // Exit to after the labeled 'for' } @assert(false) // Unreachable } }

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 { // Fields separated by commas struct MyStruct { name: string, val1: bool } struct MyStruct1 { x: s32, y, z: s32, val: bool, myS: MyStruct } }

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 tuple.y = 2.0 @assert(tuple.x == 1.0) @assert(tuple.y == 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' } let v = MyStruct{} // Initializing struct with default values @assert(v.x == 666) @assert(v.y == "454") }

Struct Initialization

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

#test { struct MyStruct { x, y: s32 = 1 // Both 'x' and 'y' initialized to 1 } // Default initialization let v0: MyStruct @assert(v0.x == 1) @assert(v0.y == 1) // Positional initialization let v1: MyStruct{10, 20} @assert(v1.x == 10) @assert(v1.y == 20) // Named initialization let v2 = MyStruct{y: 20} @assert(v2.x == 1) @assert(v2.y == 20) // Tuple initialization let v3: MyStruct = {10, 20} @assert(v3.x == 10) @assert(v3.y == 20) }

Const Structs

A struct can be defined as a constant, provided its values can be evaluated at compile time. This ensures immutability throughout program execution.

#test { struct MyStruct { x: s32 = 666 y: string = "454" } const X: MyStruct{50, "value"} #assert(X.x == 50) // Compile-time assertion #assert(X.y == "value") }

Structs as Function Arguments

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

struct Struct3 { x, y, z: s32 = 666 } func toto(v: Struct3) { @assert(v.x == 5) @assert(v.y == 5) @assert(v.z == 666) } func titi(v: Struct3) { @assert(v.x == 5) @assert(v.y == 666) } #test { // Calling with explicit values toto(Struct3{5, 5, 666}) // Type inferred from arguments toto({5, 5, 666}) // Partial initialization titi({5}) titi({5, 666}) // Named field initialization titi({x: 5, z: 5}) }

Impl

Struct Methods and Constants

In Swag, structs can encapsulate methods and constants within them using the impl block. This keeps related functionality close to the data it operates on.

#[Swag.ExportType("methods")] // Enable method reflection for this struct type struct MyStruct { x: s32 = 5 // Default 5 y: s32 = 10 // Default 10 z: s32 = 20 // Default 20 } impl MyStruct { const MyConst = true // Constant in the struct's namespace func returnTrue() => true }

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

#test { @assert(MyStruct.MyConst) @assert(MyStruct.returnTrue()) }

Multiple impl Blocks

Swag allows multiple impl blocks for the same struct. Inside methods, me refers to the current instance. You can also name the receiver explicitly.

impl MyStruct { // 'me' is implicitly 'var me: MyStruct' func returnX(me) => me.x func returnY(me) => me.y // Explicit receiver name and type func returnZ(self: MyStruct) => self.z }

Method Syntax Sugar

Using mtd makes the first parameter implicitly me. Using 'mtd const' makes it 'const me'. This simplifies common patterns.

impl MyStruct { mtd methodReturnX() => me.x // Equivalent to 'func methodReturnX(me) => me.x' func funcReturnX(me) => me.x func funcReturnY(me) => .x // 'me' can be omitted } #test { var c: MyStruct @assert(c.returnX() == 5) @assert(c.methodReturnX() == 5) @assert(c.funcReturnX() == 5) @assert(c.returnY() == 10) @assert(c.returnZ() == 20) }

Method Reflection

To enable reflection on methods in an impl block, annotate the struct with #[Swag.ExportType("methods")]. The typeinfo for the struct then exposes a methods array that you can traverse to retrieve function pointers.

#test { // Type alias for a function pointer taking 'MyStruct' and returning 's32' alias Lambda = func(MyStruct)->s32 var fnX: Lambda var fnY: Lambda var fnZ: Lambda let t = MyStruct foreach p in t.methods { // 'p.value' is the function pointer; cast it to the expected type switch p.name { case "returnX": fnX = cast(Lambda) p.value case "returnY": fnY = cast(Lambda) p.value case "returnZ": fnZ = cast(Lambda) p.value } } let v: MyStruct @assert(fnX(v) == 5) @assert(fnY(v) == 10) @assert(fnZ(v) == 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 lets you manually specify the memory offset of a field — useful for custom memory layouts, such as overlapping fields or sharing memory space between fields.

#test { struct MyStruct { x: s32 // 'y' is located at the same offset as 'x', so they share the same memory. // Changing one reflects in the other (overlay behavior). #[Swag.Offset("x")] y: s32 } // Even with two fields, they overlap, so the struct uses only 4 bytes. #assert(#sizeof(MyStruct) == 4) var v = MyStruct{} v.x = 666 // Since 'x' and 'y' share memory, updating 'x' updates 'y'. @assert(v.y == 666) }

Using Swag.Offset for Indexed Field Access

Here, Swag.Offset is used so an indexed array aliases multiple fields, enabling indexed access to those fields via a single view.

#test { struct MyStruct { x, y, z: f32 // 'idx' aliases the same memory as 'x', 'y', and 'z'. // Access 'x', 'y', and 'z' through indexed reads/writes on 'idx'. #[Swag.Offset("x")] idx: [3] f32 } var v: MyStruct v.x = 10; v.y = 20; v.z = 30 // Each index in 'idx' maps directly to x/y/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. 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 reducing the packing of struct fields using the #[Swag.Pack] attribute. This attribute specifies the alignment value 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 correct alignment 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 to 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. Use it to meet 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

Set specific alignment values for individual fields using the #[Swag.Align] attribute. This provides fine-grained control over memory layout for 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 // Two properties, x and y, of type s32 (signed 32-bit integer) } // Type aliases used in the examples below 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 // Implementation: lifecycle, accessors, conversions, comparisons, assignments, // indexing, operators, and iteration hooks impl Struct { // ------------------------------------------------------------------------- // Lifecycle hooks // ------------------------------------------------------------------------- // Called whenever a variable of this struct is about to be destroyed (C++ destructor-like) func opDrop(me) {} // Invoked after a raw copy operation has been performed from one value to another func opPostCopy(me) {} // Called after a move semantic operation has been executed from one value to another func opPostMove(me) {} // ------------------------------------------------------------------------- // Element & slice access // ------------------------------------------------------------------------- // Access a value by slicing with the [low..up] notation. Returns a string or a slice. func opSlice(me, 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(me, index: OneType)->WhateverType { return true; } // Called when @countof is used (typically in a 'for' block) to return the count of elements func opCount(me)->u64 { return 0; } // Called when @dataof is used; returns a pointer to the underlying data of type WhateverType func opData(me)->#null *WhateverType { return null; } // ------------------------------------------------------------------------- // Conversions (cast) // ------------------------------------------------------------------------- // Custom casting between the struct and another type; can be overloaded with different return types // Example: var x = cast(OneType) v #[Swag.Overload] func opCast(me)->OneType { return true; } #[Swag.Overload] func opCast(me)->AnotherType { return 0; } // ------------------------------------------------------------------------- // Equality & ordering // ------------------------------------------------------------------------- // Compare the struct value with another; used in '==' and '!=' operations #[Swag.Overload] func opEquals(me, other: OneType)->bool { return false; } #[Swag.Overload] func opEquals(me, other: AnotherType)->bool { return false; } // Three-way comparison; returns -1, 0, or 1 (used for '<', '>', '<=', '>=', '<=>') #[Swag.Overload] func opCmp(me, other: OneType)->s32 { return 0; } #[Swag.Overload] func opCmp(me, other: AnotherType)->s32 { return 0; } // ------------------------------------------------------------------------- // Assignment // ------------------------------------------------------------------------- // Direct assignment via '=' #[Swag.Overload] func opAffect(me, other: OneType) {} #[Swag.Overload] func opAffect(me, other: AnotherType) {} // Assign a literal value with a specific suffix to the struct (generic) #[Swag.Overload] func(suffix: string) opAffectLiteral(me, value: OneType) {} #[Swag.Overload] func(suffix: string) opAffectLiteral(me, value: AnotherType) {} // Assign to an indexed position via '[?] =' #[Swag.Overload] func opIndexAffect(me, index: OneType, value: OneType) {} #[Swag.Overload] func opIndexAffect(me, index: OneType, value: AnotherType) {} // ------------------------------------------------------------------------- // Operators (binary, unary, compound assignment) // ------------------------------------------------------------------------- // Binary operation with 'op' representing the operator as a string // Examples: '+', '-', '*', '/', '%', '|', '&', '^', '<<', '>>' #[Swag.Overload] func(op: string) opBinary(me, other: OneType)->Struct { return {1, 2}; } #[Swag.Overload] func(op: string) opBinary(me, other: AnotherType)->Struct { return {1, 2}; } // Unary operation with 'op' representing the operator as a string // Examples: '!', '-', '~' func(op: string) opUnary(me)->Struct { return {1, 2}; } // Assignment with operator 'op' (compound assignments) // Examples: '+=', '-=', '*=', '/=', '%=', '|=', '&=', '^=', '<<=', '>>=' #[Swag.Overload] func(op: string) opAssign(me, other: OneType) {} #[Swag.Overload] func(op: string) opAssign(me, other: AnotherType) {} // Indexed assignment with operator 'op' #[Swag.Overload] func(op: string) opIndexAssign(me, index: OneType, value: OneType) {} #[Swag.Overload] func(op: string) opIndexAssign(me, index: OneType, value: AnotherType) {} // ------------------------------------------------------------------------- // Iteration (foreach support) // ------------------------------------------------------------------------- // 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(me, stmt: #code void) {} func(ptr: bool, back: bool) opVisitWhatever(me, stmt: #code void) {} func(ptr: bool, back: bool) opVisitAnother(me, stmt: #code void) {} } }

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 and control how your struct responds.

struct Struct { x, y, z: s32 = 666 // Fields with default value 666 } impl Struct { // Overload for 's32' #[Swag.Overload] mtd opAffect(value: s32) { me.x, me.y = value } // Overload for 'bool' #[Swag.Overload] mtd opAffect(value: bool) { me.x, me.y = value ? 1 : 0 } } #test { let v: Struct = 4's32 @assert(v.x == 4) @assert(v.y == 4) @assert(v.z == 666) var v1: Struct = true @assert(v1.x == 1) @assert(v1.y == 1) v1 = false @assert(v1.x == 0) @assert(v1.y == 0) }

Optimizing Initialization with Swag.Complete

When opAffect completely initializes the struct, mark it with #[Swag.Complete]. This avoids default initialization before assignment for better performance.

impl Struct { #[Swag.Complete, Swag.Overload] mtd opAffect(value: u64) { me.x, me.y, me.z = cast(s32) value } #[Swag.Implicit, Swag.Overload] mtd opAffect(value: u16) { me.x, me.y = cast(s32) value } } #test { let v: Struct = 2'u64 @assert(v.x == 2) @assert(v.y == 2) @assert(v.z == 2) }

Handling Function Arguments and Automatic Conversion

Function arguments are not automatically converted through opAffect unless Swag.Implicit is used. Otherwise, an explicit cast is required.

#test { func toto(v: Struct) { @assert(v.x == 5) @assert(v.y == 5) @assert(v.z == 666) } func titi(v: Struct) { @assert(v.y == 666) } // Explicit cast triggers 'opAffect(s32)' toto(cast(Struct) 5's32) // Implicit conversion via 'opAffect(u16)' toto(5'u16) }

Using opAffect in Constant Expressions

To allow compile-time initialization through opAffect, mark it with #[Swag.ConstExpr].

struct Vector2 { x, y: f32 } impl Vector2 { #[Swag.ConstExpr] mtd opAffect(one: f32) { me.x, me.y = one } } 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 specifies how many iterations a loop performs when looping over a struct instance. By defining it, you make the struct act like an iterable object with a controlled length.

impl MyStruct { // Return the number of iterations for loops using this struct mtd opCount() => 4'u64 }

With opCount defined, an instance of MyStruct can be used in loops just like arrays or other iterable types. The loop executes the number of times returned by opCount.

#test { let v = MyStruct{} @assert(@countof(v) == 4) var cpt = 0 for v do cpt += 1 @assert(cpt == 4) }

Custom Iteration

struct MyStruct { x: s32 = 10 y: s32 = 20 z: s32 = 30 }

Introduction to opVisit

opVisit is a flexible macro for iterating over elements of a struct or any data it owns (dynamic arrays, buffers, object graphs). The #[Swag.Macro] attribute is mandatory.

opVisit is generic over two compile-time booleans: - ptr: if true, elements are visited by pointer (address). - back: if true, elements are visited in reverse order.

impl MyStruct { #[Swag.Macro] func(ptr: bool, back: bool) opVisit(me, stmt: #code void) { #if ptr do #error("Visiting by pointer is not supported in this example.") #if back do #error("Reverse visiting is not supported in this example.") // Visit fields x, y, z in declaration order for idx in 3 { #macro { var #alias0: s32 = undefined switch #up idx { case 0: #alias0 = #up me.x case 1: #alias0 = #up me.y case 2: #alias0 = #up me.z } var #alias1 = @index #inject(#up stmt) } } } }

Iterating Over Struct Fields

Example usage of opVisit to traverse fields of a struct.

#test { var myStruct = MyStruct{} var cpt = 0 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

An alternative macro that visits fields in reverse order.

impl MyStruct { #[Swag.Macro] mtd(ptr: bool, back: bool) opVisitReverse(stmt: #code void) { for idx in 3 { #macro { var #alias0: s32 = undefined switch #up idx { case 0: #alias0 = #up me.z case 1: #alias0 = #up me.y case 2: #alias0 = #up me.x } var #alias1 = @index #inject(#up stmt) } } } }

Reverse Order Iteration

Using opVisitReverse to iterate fields from last to first.

#test { var myStruct = MyStruct{} var cpt = 0 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 fields, opVisit can target owned collections such as dynamic arrays.

struct SliceStruct { buffer: [?] s32 = [1, 2, 3, 4, 5] }

Custom opVisit for Dynamic Arrays

Iterate over buffer elements instead of struct fields.

impl SliceStruct { #[Swag.Macro] func(ptr: bool, back: bool) opVisit(me, stmt: #code void) { for idx in @countof(me.buffer) { #macro { var #alias0 = #up me.buffer[#up idx] var #alias1 = @index #inject(#up stmt) } } } }

Iterating Over a Dynamic Array

Sum elements of a slice via opVisit.

#test { var arrStruct = SliceStruct{} var sum = 0 foreach v, i in arrStruct do sum += v @assert(sum == 1 + 2 + 3 + 4 + 5) }

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 typically wouldn't require move semantics (no heap allocation), this illustrates how these features can be implemented and used in Swag.

struct Vector3 { x, y, z: s32 = 666 } impl Vector3 { // Called after a copy; customize post-copy behavior here. mtd opPostCopy() { .x, .y, .z += 1 } // Called after a move; customize post-move behavior here. mtd opPostMove() { .x, .y, .z += 2 } // Called before destruction; place cleanup here if needed. mtd opDrop() {} } #test { var a = Vector3{} // Default init. var b = Vector3{100, 200, 300} // Custom init. // Copy semantics: drop 'a' (if needed), copy 'b' to 'a', then call 'opPostCopy' on 'a'. a = b @assert(a.x == 101) @assert(a.y == 201) @assert(a.z == 301) // Move semantics with '#move': drop 'a' (if needed), move 'b' to 'a', then call 'opPostMove' on 'a'. // With 'opDrop' present, 'b' is reinitialized to defaults (666). a = #move b @assert(a.x == 102) @assert(a.y == 202) @assert(a.z == 302) @assert(b.x == 666) // '#nodrop' skips the initial drop of 'a'. a = #nodrop b // Copy without dropping 'a' first. a = #nodrop #move b // Move without dropping 'a' first. // '#moveraw' prevents reinitialization of the source after move. Use with care. a = #moveraw b a = #nodrop #moveraw b }

Move Semantics in Functions

Move semantics can be expressed in function parameters using && instead of &.

#test { // Move overload: takes ownership of 'from' and moves into 'assignTo'. #[Swag.Overload] func assign(assignTo: &Vector3, from: &&Vector3) { assignTo = #move from } // Copy overload: copies 'from' into 'assignTo'. #[Swag.Overload] func assign(assignTo: &Vector3, from: Vector3) { assignTo = from } var a = Vector3{1, 2, 3} var b: Vector3 // Copy path. assign(&b, a) @assert(b.x == 2 and b.y == 3 and b.z == 4) // +1 via 'opPostCopy'. @assert(a.x == 1 and a.y == 2 and a.z == 3) // 'a' unchanged. // Move path using 'moveref'. assign(&b, moveref &a) @assert(b.x == 3 and b.y == 4 and b.z == 5) // +2 via 'opPostMove'. @assert(a.x == 666 and a.y == 666 and a.z == 666) // 'a' reset to defaults after move. }

Custom Literals

User-Defined Literals

User-defined literals extend literal meaning so custom types can be initialized directly with unit-like suffixes (e.g., 4ms'). This example defines a Duration type that stores seconds and accepts seconds, milliseconds, minutes, and hours.

Literal Suffixes

A literal suffix immediately follows a numeric literal to indicate a unit or type, e.g., 4'ms means 4 milliseconds.

To support user-defined literals, provide: 1) a type (e.g., Duration), and 2) methods to convert the numeric value according to the suffix.

// Represents a delay, expressed in seconds. struct Duration { timeInSeconds: f32 } impl Duration { // Initialize from milliseconds without a suffix (implicit path) #[Swag.ConstExpr, Swag.Implicit, Swag.Inline] mtd opAffect(valueMs: s32) { me.timeInSeconds = valueMs / 1000.0 } }

Use the special function opAffectLiteral to convert a value plus suffix.

impl Duration { // Handle literals like '5's', '500'ms', '2'min', '1'h' #[Swag.ConstExpr, Swag.Implicit, Swag.Inline] mtd(suffix: string) opAffectLiteral(value: s32) { #if suffix == "s" do me.timeInSeconds = value #elif suffix == "ms" do me.timeInSeconds = value / 1000.0 #elif suffix == "min" do me.timeInSeconds = value * 60.0 #elif suffix == "h" do me.timeInSeconds = value * 3600.0 #else do #error("invalid duration literal suffix '" ++ suffix ++ "'") } }

You can then place the suffix right after the numeric literal when the type is Duration.

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

Interface

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

Unlike C++, the virtual table is not embedded in the struct; it is a separate object. This lets you implement an interface for a struct without changing the struct.

struct Point2 { x, y: f32 } struct Point3 { x, y, z: f32 }

Interface Declaration

Declare interface IReset with two functions set and reset.

interface IReset { // First parameter must be 'me' when using 'func' func set(me, val: f32); // 'mtd' avoids specifying 'me' mtd reset(); }

Implementing an Interface

Implement interface IReset for Point2.

impl IReset for Point2 { // Mark with 'impl' to implement an interface function mtd impl set(val: f32) { me.x = val me.y = val + 1 } // 'mtd' is sugar; 'func' also works func impl reset(me) { me.x, me.y = 0 } // Regular methods may also appear in this 'impl' mtd myOtherMethod() {} }

Implementing the Interface for Another Struct

Implement IReset for Point3.

impl IReset for Point3 { mtd impl set(val: f32) { me.x = val me.y = val + 1 me.z = val + 2 } mtd impl reset() { me.x, me.y, me.z = 0 } }

Using the Interface

Cast to the interface and call its methods.

#test { var pt2: Point2 var pt3: Point3 var itf = cast(IReset) pt2 itf.set(10) @assert(pt2.x == 10) @assert(pt2.y == 11) itf = cast(IReset) pt3 itf.set(10) @assert(pt3.x == 10) @assert(pt3.y == 11) @assert(pt3.z == 12) itf.reset() @assert(pt3.x == 0 and pt3.y == 0 and pt3.z == 0) }

Accessing Interface Methods Directly

Implementation functions live under a scope named after the interface.

#test { var pt2: Point2 var pt3: Point3 pt2.IReset.set(10) pt2.IReset.reset() pt3.IReset.set(10) pt3.IReset.reset() }

Interface as a Type

An interface occupies two pointers: object pointer + virtual table pointer.

#test { var pt2: Point2 var pt3: Point3 var itf = cast(IReset) pt2 #assert(#sizeof(itf) == 2 * #sizeof(*void)) // Retrieve the concrete type with '@kindof' itf = cast(IReset) pt2 @assert(@kindof(itf) == Point2) itf = cast(IReset) pt3 @assert(@kindof(itf) == Point3) // Retrieve the concrete data with '@dataof' itf = cast(IReset) pt2 @assert(@dataof(itf) == &pt2) itf = cast(IReset) pt3 @assert(@dataof(itf) == &pt3) }

Default Implementation in Interfaces

Provide default bodies directly in the interface. If a struct does not override, the default is used.

interface ITest { mtd isImplemented()->bool { return false } }

Override on Point2; no override on Point3.

impl ITest for Point2 { mtd impl isImplemented()->bool { return true } } impl ITest for Point3 { }

For Point3, isImplemented() returns false (the default).

#test { var v2: Point2 var v3: Point3 let i2 = cast(ITest) v2 @assert(i2.isImplemented()) let i3 = cast(ITest) v3 @assert(!i3.isImplemented()) }

Functions

Declaration

Introduction to Function Declarations

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

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

Returning Values from Functions

If a function returns a value, use -> followed by the return type. The body must contain a return statement.

func toto1()->s32 { return 0 }

Inferring Return Types

Use => for simple expressions when the return type can be inferred automatically.

func sum(x, y: s32) => x + y

Shorter Syntax for Functions Without Return Values

Functions that do not return a value can use the same concise => syntax.

func print(val: string) => @print(val)

Defining Parameters in Functions

Parameters are declared within parentheses after the function name, each with a name and type.

func sum1(x, y: s32, unused: f32)->s32 { return x + y }

Using Default Parameter Values

Parameters may have default values, used when not provided by the caller.

func sum2(x, y: s32, unused: f32 = 666)->s32 { return x + y }

Inferred Parameter Types

If a parameter has a default value, its type can be inferred from it.

func sum3(x, y = 0.0) { #assert(#typeof(x) == f32) #assert(#typeof(y) == f32) }

Overloading Functions

Multiple functions can share the same name if they differ in parameter count or types.

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

Nested Functions

Functions can be nested within other functions for local logic organization. Nested functions are scoped, not closures.

#test { func sub(x, y: s32) => x - y let x = sub(5, 2) @assert(x == 3) }

Named Parameters and Parameter Order

Named parameters allow calling functions with arguments in any order.

#test { func sub(x, y: s32) => x - y { let x1 = sub(x: 5, y: 2) @assert(x1 == 3) let x2 = sub(y: 5, x: 2) @assert(x2 == -3) } { func returnMe(x, y: s32 = 0) => x + y * 2 @assert(returnMe(x: 10) == 10) @assert(returnMe(y: 10) == 20) } }

Returning Multiple Values with Anonymous Structs

Functions can return anonymous structs to conveniently hold multiple values. These can be accessed directly or destructured.

#test { func myFunction()->{ x, y: f32 } { return {1.0, 2.0} } let result = myFunction() @assert(result.item0 == 1.0) @assert(result.item1 == 2.0) let (x, y) = myFunction() @assert(x == 1.0) @assert(y == 2.0) let (z, w) = myFunction() @assert(z == 1.0) @assert(w == 2.0) }

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—enabling functional programming patterns.

#test { func myFunction0() {} func myFunction1(x: s32) => x * x let ptr0: func() = &myFunction0 ptr0() let ptr1 = &myFunction1 @assert(myFunction1(2) == 4) @assert(ptr1(2) == 4) }

Null Lambdas

A lambda can be null, representing an uninitialized or absent function pointer. This is useful for optional callbacks or deferred initialization.

#test { var lambda: func()->bool @assert(lambda == null) }

Using Lambdas as Function Parameters

Lambdas can be passed as arguments, enabling higher-order functions that call other functions dynamically.

#test { alias Callback = func(s32)->s32 func toDo(value: s32, ptr: Callback)->s32 => ptr(value) func square(x: s32) => x * x @assert(toDo(4, &square) == 16) }

Anonymous Functions

Anonymous (inline) functions can be defined without names for short, inline logic.

#test { var cb = func(x: s32)->s32 => x * x @assert(cb(4) == 16) cb = func(x: s32)->s32 => x * x * x @assert(cb(4) == 64) }

Passing Anonymous Functions as Parameters

Anonymous functions can be passed directly as arguments.

#test { alias Callback = func(s32)->s32 func toDo(value: s32, ptr: Callback)->s32 => ptr(value) @assert(toDo(4, func(x: s32) => x * x) == 16) @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

If the type is clear from context, lambda parameters can omit explicit types.

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

Omitting Types When Assigning Lambdas

When the variable type is known, parameter and return types can be omitted in the lambda.

#test { var fct: func(s32, s32)->bool fct = func(x, y) => x == y @assert(fct(10, 10)) fct = func(x, y) { return x != y } @assert(fct(20, 120)) }

Lambdas with Default Parameter Values

Lambdas may have parameters with default values, allowing flexible invocation.

#test { { let x = func(val = true) { @assert(val == true) } x() } { var x: func(val: bool = true) x = func(val) { @assert(val == true) } x() x(true) } }

Closure

Introduction to Closures in Swag

Swag supports limited closures, allowing functions to capture variables from their surrounding scope. Up to 48 bytes can be captured without heap allocation. Only simple types (without opDrop, opPostCopy, or opPostMove) can be captured.

Declaring a Closure

A closure is declared like a lambda, with captured variables listed between |...| before the parameter list. The type uses func||(...).

#test { let a = 125 let b = 521 let fct: func||() = func|a, b|() { @assert(a == 125) @assert(b == 521) } fct() }

Capturing Variables by Reference

Use & to capture by reference; otherwise, capture is by value.

#test { var a = 125 let fct: func||() = func|&a|() { a += 1 } fct() @assert(a == 126) fct() @assert(a == 127) }

Assigning Lambdas to Closure Variables

A closure variable can also hold a regular lambda (no captures).

#test { var fct: func||(s32, s32)->s32 fct = func(x, y) => x + y @assert(fct(1, 2) == 3) }

Capturing Complex Types

Arrays, structs, and slices can be captured by value if they fit in the capture size and are plain data (POD).

#test { var x = [1, 2, 3] var fct: func||(s32)->s32 fct = func|x|(toAdd) { var res = 0 foreach v in x do res += v res += toAdd return res } let result = fct(4) @assert(result == 1 + 2 + 3 + 4) }

Modifying Captured Variables

Captured values belong to the closure and can be mutated, enabling stateful behavior.

#test { func getInc()->func||()->s32 { let x = 10 return func|x|()->s32 { x += 1 return x } } let fct = getInc() @assert(fct() == 11) @assert(fct() == 12) @assert(fct() == 13) }

Mixin

Introduction to Swag Mixins

A mixin in Swag is declared similarly to a function but with the attribute #[Swag.Mixin]. Mixins inject code into the caller's scope, manipulate variables, or execute as if written in that scope. This file provides a clear set of examples and tests.

#test { #[Swag.Mixin] func myMixin() { // Basic empty mixin } }

Basic Example of a Mixin

A mixin can directly modify variables in the caller's scope. This example increments a by 1 each time it is called.

#test { #[Swag.Mixin] func myMixin() { a += 1 } var a = 0 myMixin() // Equivalent to writing 'a += 1' directly here myMixin() @assert(a == 2) }

Mixins with Parameters

Mixins behave like functions: they can take parameters, default values, and return values. Here, increment defaults to 1.

#test { #[Swag.Mixin] func myMixin(increment: s32 = 1) { a += increment } var a = 0 myMixin() // Uses default: 'a += 1' myMixin(2) // Uses provided value: 'a += 2' @assert(a == 3) }

Mixins with Code Blocks

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

#test { #[Swag.Mixin] func doItTwice(what: #code void) { #inject(what) // first execution #inject(what) // second execution } var a = 0 doItTwice(#code { a += 1; }) @assert(a == 2) }

Passing Code Blocks in Separate Statements

When the last parameter is of type code, the code can be provided in a separate block after the call.

#test { #[Swag.Mixin] func doItTwice(value: s32, what: #code void) { #inject(what) #inject(what) } var a = 0 // Inline code argument doItTwice(4, #code { a += value; }) // Separate trailing block doItTwice(2) { a += value } @assert(a == 12) }

Creating Aliases with Mixins

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 } var a, b = 0 inc10(|a|) // use 'a' as the alias inc10(|b|) // use 'b' as the alias @assert(a == b and b == 10) } #test { #[Swag.Mixin] func setVar(value: s32) { let #alias0 = value } setVar(|a| 10) setVar(|b| 20) @assert(a == 10) @assert(b == 20) // No explicit alias: default '#alias0' is used setVar(30) @assert(#alias0 == 30) }

Unique Variable Names with #uniq?

Mixins can declare special variables named #uniq?. Each invocation receives a unique symbol, avoiding naming conflicts and allowing multiple calls in the same scope.

#test { var total: s32 #[Swag.Mixin] func toScope() { var #uniq0: s32 = 1 total += #uniq0 } toScope() toScope() toScope() @assert(total == 3) }

Macro

Introduction to Swag Macros

Macros in Swag are declared like functions, but with the #[Swag.Macro] attribute. They are expanded at compile time and can be reused to generate code.

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

Macro Scope

Macros run in their own scope, isolated from the caller. Unlike mixins, they do not share the caller's scope. Variables defined inside a macro cannot interfere with the caller unless explicitly elevated.

#test { #[Swag.Macro] func myMacro() { // 'a' here is local to the macro var a = 666 } let a = 0 myMacro() // no conflict with outer 'a' @assert(a == 0) }

Resolving Identifiers Outside the Macro Scope

Use #up to explicitly reference and modify variables from the caller's scope.

#test { #[Swag.Macro] func myMacro() { #up a += 1 // increments the caller's 'a' } var a = 0 myMacro() myMacro() @assert(a == 2) }

Macros with #code Parameters

Macros can accept #code parameters to inject caller-provided code.

#test { #[Swag.Macro] func myMacro(what: #code void) { #inject(what) // insert provided code } var a = 0 // Use '#code' to pass a block or expression as code (not evaluated at call site) myMacro(#code { #up a += 1; }) // If the last parameter is '#code void', the following statement becomes the last argument myMacro() { #up a += 1 } @assert(a == 2) }

Typed #code Parameters

A #code parameter can be typed: - Use '#code void' for a code statement. - Use '#code <type>' for a code expression that returns a specific type.

#test { #[Swag.Macro] func myMacro(what: #code bool) { @assert(#inject(what)) } // The expression is transformed to code and evaluated only after injection myMacro(1 == 1) myMacro(3 > 2 and 2 < 4) myMacro(true) // Note: '#code' creates an untyped piece of code usable with any argument type myMacro(#code true) }

Forcing Code into the Caller’s Scope with #macro

Use #macro to run injected code as if it were in the caller's scope, avoiding the need for #up inside the injected block.

#test { #[Swag.Macro] func myMacro(what: #code void) { var a = 666 // macro-local 'a' #macro { // references the caller's scope #inject(#up what) } } var a = 1 myMacro() { a += 2 // operates on caller's 'a' due to '#macro' } @assert(a == 3) }

Performance Considerations with Macros

Macros extend the language without function-pointer/lambda overhead. They can be used to generate tight loops with caller-visible indices, etc.

#test { #[Swag.Macro] func repeat(count: s32, what: #code void) { var a = 0 while a < count { #macro { var index = #up a // 'index' visible in caller via macro scope #inject(#up what) // insert caller code } a += 1 } } var a = 0 repeat(5) { a += index // sum 0..4 } @assert(a == 0 + 1 + 2 + 3 + 4) repeat(3) { a += index // add 0..2 } @assert(a == 10 + 3) }

Handling break and continue in User Code with Macros

You can remap break/continue in injected user code to control which loop they target.

#test { #[Swag.Macro] func repeatSquare(count: u32, what: #code void) { // Define a scope label used as the break target #scope(ScopeTarget) for count { for count { #macro { // Remap 'break' in user code to 'break to ScopeTarget' (outer loop exit) #inject(#up what, break = break to ScopeTarget) } } } } var a = 0 repeatSquare(5) { a += 1 if a == 10 do break // remapped to 'break to ScopeTarget' } @assert(a == 10) }
Another example:

Remap both break and continue to influence outer/inner loop flow.

#test { #[Swag.Macro] func repeatSquare(count: u32, what: #code void) { // Label for controlling the outer loop #scope(Outer) for count { for count { #macro { // 'break' -> exit outer loop; 'continue' -> skip inner iteration #inject(#up what, break = break to Outer, continue = break) } } } } var a = 0 var b = 0 repeatSquare(5) { a += 1 // If 'a' divisible by 3, skip to next inner iteration (acts like 'continue') if a % 3 == 0 do continue b += 1 // Exit both loops when 'a' reaches 8 if a == 8 do break } @assert(a == 8) @assert(b == 6) }

Using Aliases in Macros

Special variables #alias<num> inside a macro can be overridden with named aliases at the call site for clearer code.

#test { #[Swag.Macro] func call(v: s32, stmt: #code void) { let #alias0 = v let #alias1 = v * 2 #inject(stmt) } call(20) { @assert(#alias0 == 20) @assert(#alias1 == 40) } call(|x| 20) { @assert(x == 20) // 'x' aliases '#alias0' @assert(#alias1 == 40) // '#alias1' unchanged } call(|x, y| 20) { @assert(x == 20) // replaces '#alias0' @assert(y == 40) // replaces '#alias1' } }

Variadic Parameters

Introduction to Variadic Functions

Variadic functions accept a variable number of arguments using .... They allow flexibility in cases where the number of arguments is not known in advance.

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

Working with Variadic Parameters as Slices

Variadic parameters are treated as slices of type any, allowing you to process mixed argument types dynamically.

#test { func myFunction(parameters: ...) { // Check the number of arguments @assert(@countof(parameters) == 3) // Initially, each parameter is of type 'any' #assert(#typeof(parameters[0]) == any) #assert(#typeof(parameters[1]) == any) #assert(#typeof(parameters[2]) == any) // Determine actual runtime types @assert(@kindof(parameters[0]) == s32) @assert(@kindof(parameters[1]) == string) @assert(@kindof(parameters[2]) == f32) } myFunction(4, "true", 5.6) }

Forcing Variadic Parameters to a Specific Type

When all arguments are of the same type, you can enforce it using type annotations. This prevents parameters from defaulting to any.

#test { func myFunction(value: bool, parameters: s32...) { // All 'parameters' elements 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) // Check values @assert(parameters[0] == 10) @assert(parameters[1] == 20) @assert(parameters[2] == 30) @assert(parameters[3] == 40) } myFunction(true, 10, 20, 30, 40) }

Passing Variadic Parameters Between Functions

Variadic parameters can be forwarded between functions while preserving their types and values.

#test { func A(params: ...) { @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: ...) { A(params) // Forward the parameters } B("value", true) }

Combining Fixed and Variadic Parameters

You can mix fixed parameters with variadic ones to make function calls more expressive.

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

Handling Different Types in Variadic Parameters

Handle mixed-type parameters dynamically, performing type-specific actions.

#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 (Uniform Function Call Syntax) allows a function to be called in the param.func() form when the first parameter type of func() matches param. This enables calling standalone functions as if they were instance methods, improving readability.

#test { func myFunc(param: bool) => param let b = false @assert(myFunc(b) == b.myFunc()) // UFCS allows method-like syntax }

Static Functions as Methods

All functions in Swag are static, but UFCS enables them to be invoked with instance-style syntax. This improves clarity when working with structs or objects.

#test { struct Point { x, y: s32 } func set(pt: *Point, value: s32) { pt.x, pt.y = value } var pt: Point // UFCS — called like a method 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 with multi-parameter functions as long as the first parameter type matches the instance type.

#test { struct Vector { x, y: f32 } func add(vec: *Vector, dx: f32, dy: f32) { vec.x += dx vec.y += dy } var v: Vector // UFCS style v.add(1.0, 2.0) @assert(v.x == 1.0 and v.y == 2.0) // Standard function call add(&v, 3.0, 4.0) @assert(v.x == 4.0 and v.y == 6.0) }

UFCS and Function Overloading

UFCS supports overloaded functions, selecting the correct overload based on argument types.

#test { struct Complex { real, imag: f32 } #[Swag.Overload] func multiply(c: *Complex, scalar: f32) { c.real *= scalar c.imag *= scalar } #[Swag.Overload] func multiply(c: *Complex, other: *Complex) { // Use temporary variables to prevent reuse of modified values let r = (c.real * other.real) - (c.imag * other.imag) let i = (c.real * other.imag) + (c.imag * other.real) c.real = r c.imag = i } var c1 = Complex{2.0, 3.0} var c2 = Complex{4.0, 5.0} // UFCS: multiply by scalar c1.multiply(2.0) @assert(c1.real == 4.0 and c1.imag == 6.0) // UFCS: multiply by another complex number c1.multiply(&c2) @assert(c1.real == -14.0 and c1.imag == 44.0) }

Constexpr

Swag.ConstExpr Functions

Functions marked with #[Swag.ConstExpr] can be executed at compile time if their inputs are known. The compiler computes their results during compilation, embedding the values directly into the code for zero runtime overhead.

#[Swag.ConstExpr] func sum(x, y: f32) => x + y

Example: Compile-Time Computation

The compiler executes 'sum(1, 2)' at compile time, embedding the result directly in G.

const G = sum(1, 2) #assert(G == 3)

Example: Using Swag.ConstExpr with Complex Expressions

ConstExpr functions can evaluate compound arithmetic expressions at compile time.

#[Swag.ConstExpr] func complexCalc(a, b, c: f32) => (a + b) * c / 2 const result = complexCalc(4, 5, 6) #assert(result == 27.0)

Example: Compile-Time Execution of Array Initializations

ConstExpr functions can initialize arrays and collections at compile time.

#[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

#run forces compile-time execution even for functions not marked as ConstExpr.

func mul(x, y: f32) => x * y const G1 = #run mul(3, 6) #assert(G1 == 18)

Example: Compile-Time Evaluation of Conditional Logic

You can evaluate conditions and branches at compile time using ConstExpr functions.

#[Swag.ConstExpr] func max(a, b: s32) => a > b ? a : b const MaxValue = max(10, 20) #assert(MaxValue == 20)

Example: Compile-Time Initialization of Structs

ConstExpr functions can construct and initialize user-defined structs during compilation.

#[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)

Example: Using #run with User-Defined Types

#run can be used to execute ordinary functions with structs at compile time.

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)

Function Overloading

Function Overloading with Swag.Overload

Swag allows multiple functions to share the same name when their parameter signatures differ. This feature, called function overloading, enables writing concise and intuitive APIs. To activate it, each version of the function must be decorated with #[Swag.Overload].

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

The compiler chooses the correct overload based on the number and types of arguments. This allows calling sum naturally for different scenarios.

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

Discard

Return Value Usage

Swag enforces that all function return values must be used. If a function’s result is ignored, the compiler raises an error. This prevents accidental omission of important results and ensures deliberate handling of all return values.

#test { func sum(x, y: s32) => x + y // Uncommenting the following line would cause a compile-time error: // sum(2, 3) // Use 'discard' to explicitly ignore the return value discard sum(2, 3) }

Swag.Discardable Attribute

Marking a function with #[Swag.Discardable] allows its return value to be safely ignored. Use this for utility functions whose results are optional.

#test { #[Swag.Discardable] func mul(x, y: s32)->s32 => x * y // Return value can be ignored without using 'discard' mul(2, 4) }

Retval

The retval Special Type

In Swag, retval represents the current function’s return type. It allows you to declare and manipulate the return value directly inside the function, without repeating the type declaration. This improves code readability and flexibility, especially when working with complex or generic return types.

#test { func toto()->s32 { var result: retval // 'retval' resolves to 's32' in this context. result = 10 return result } @assert(toto() == 10) }

Optimizing Return Values

retval provides an optimization hint that allows the compiler to use the caller’s memory for the return value, reducing unnecessary copies. This is especially beneficial when returning large structs, arrays, or tuples.

#test { struct RGB { x, y, z: f64 } func getWhite()->RGB { // 'retval = undefined' prevents unnecessary clearing of the return structure. var result: retval = undefined result.x = 0.5 result.y = 0.1 result.z = 1.0 return result } let (r, g, b) = getWhite() @assert(r == 0.5) @assert(g == 0.1) @assert(b == 1.0) }

Returning Arrays Efficiently

When returning large data structures like arrays, using retval avoids redundant initialization or copying, resulting in faster, more memory-efficient code.

#test { func toto()->[255] s32 { var result: retval = undefined for i in 255 do result[i] = i return result } var arr = toto() @assert(arr[0] == 0) @assert(arr[100] == 100) @assert(arr[254] == 254) }

Foreign

Interoperability with External Modules

Swag supports calling external system or third-party library functions, such as those from Windows DLLs or shared objects on other platforms. This allows Swag applications to interact directly with system APIs or external C libraries for extended functionality.

Declaring External Functions

Use the #[Swag.Foreign("<module>")] attribute to declare functions that exist in external modules (DLLs or shared libraries). The specified module name must match the external library name.

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

Example: Windows API Integration

These declarations enable Swag code to directly call Windows API functions like Sleep or ExitProcess from kernel32.dll. For instance: - Sleep(1000) pauses execution for 1 second. - ExitProcess(0) terminates the running process cleanly.

Linking to External Libraries

To ensure external calls resolve correctly, use the #foreignlib directive. This instructs the compiler and linker to include the specified module when building the executable or shared library.

// Link the 'kernel32.dll' library for Windows builds #foreignlib("kernel32")

Special Functions

#global skip

Main Function (#main)

The #main function serves as the program's primary entry point — similar to C/C++'s main(). It is unique per module and marks where execution begins when the program starts.

#main { }

Handling Program Arguments

Command-line arguments are accessed via the intrinsic @args, which returns a slice of strings representing all arguments passed to the program.

#main { var myArgs = @args var count = @countof(myArgs) if myArgs[0] == "fullscreen" { // Example: Enable fullscreen mode here ... } }

Pre-Main Function (#premain)

The #premain function executes after all #init functions in every module have run, but before #main. It's ideal for setup tasks that depend on completed module initialization.

#premain { }

Initialization Function (#init)

#init runs during module initialization. Multiple #init functions can exist within a module and execute before #premain and #main. Execution order between multiple #init functions is undefined.

#init { }

Drop Function (#drop)

#drop acts as the cleanup counterpart to #init. It executes when a module is unloaded, releasing any resources acquired during initialization. Multiple #drop functions can exist, and they execute in reverse order relative to #init.

#drop { }

Test Function (#test)

#test is used exclusively for testing code. Functions marked with #test are executed only in test mode, allowing developers to validate functionality through assertions and controlled test cases.

#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

Fundamental intrinsics commonly needed across Swag programs.

func @assert(value: bool); // Assert that a condition is true (debugging). func @breakpoint(); // Trigger a debugger breakpoint. func @getcontext() -> *Swag.Context; // Retrieve the current execution context. func @setcontext(context: const *Swag.Context); // Set the current execution context. func @compiler() -> Swag.ICompiler; // Retrieve the current compiler interface. @panic(); // Trigger a panic, stopping program execution. @compilererror(); // Generate a compile-time error. @compilerwarning(); // Generate a compile-time warning.

Value Intrinsics

@err: any // The current raised error (null if none). @args: const [..] string // Command-line arguments passed to the program. @bytecode: bool // True if the code is being executed as bytecode. @pinfos: *Swag.ProcessInfos // Retrieve program information.

Built-in Intrinsics

Essential operations related to type and memory management for low-level or performance-critical code.

@init(); // Initialize a variable or memory area. @drop(); // Destroy a variable or memory area. @postmove(); // Post-move hook. @postcopy(); // Post-copy hook. @kindof(); // Kind of a type (e.g., primitive, struct). @countof(); // Number of elements in an array. @dataof(); // Pointer to underlying data of a type. @mkslice(); // Create a slice from a data pointer and length. @mkstring(); // Create a string from a data pointer and length. @mkany(); // Create a generic 'any' from a value. @mkinterface(); // Create an interface from an implementation. @mkcallback(); // Create a callback from a function pointer. @tableof(); // Interface table for a given type. #sizeof(); // Size in bytes of a type or variable. #alignof(); // Alignment requirement of a type. #offsetof(); // Offset in bytes of a struct field. #typeof(); // Type of an expression. #stringof(); // String representation of a type or expression. #isconstexpr(); // Check if an expression is a constant expression.

Memory Intrinsics

Memory management operations for allocation, deallocation, and manipulation.

func @alloc(size: u64) -> *void; // Allocate a block of memory. func @realloc(ptr: *void, size: u64) -> *void; // Reallocate a block of memory. func @free(ptr: *void); // Free a previously allocated block. func @memset(dst: *void, value: u8, size: u64); // Set a block of memory to a value. func @memcpy(dst: *void, src: const *void, size: u64); // Copy a block of memory. func @memmove(dst: *void, src: const *void, size: u64); // Move a block of memory (overlap-safe). func @memcmp(dst: const *void, src: const *void, size: u64) -> s32; // Compare two blocks of memory. func @strlen(value: const *u8) -> u64; // Length of a null-terminated string.

Atomic Intrinsics

Thread-safe manipulation of variables in shared memory.

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: s8, exchangeWith: s8) -> s8; func @atomcmpxchg(addr: *s16, compareTo: s16, exchangeWith: s16) -> s16; func @atomcmpxchg(addr: *s32, compareTo: s32, exchangeWith: s32) -> s32; func @atomcmpxchg(addr: *s64, compareTo: s64, exchangeWith: s64) -> s64; func @atomcmpxchg(addr: *u8, compareTo: u8, exchangeWith: u8) -> u8; func @atomcmpxchg(addr: *u16, compareTo: u16, exchangeWith: u16) -> u16; func @atomcmpxchg(addr: *u32, compareTo: u32, exchangeWith: u32) -> u32; func @atomcmpxchg(addr: *u64, compareTo: u64, exchangeWith: u64) -> u64;

Math Intrinsics

Mathematical operations including trigonometric, logarithmic, rounding, and bit utilities.

func @sqrt(value: f32) -> f32; func @sqrt(value: f64) -> f64; func @sin(value: f32) -> f32; func @sin(value: f64) -> f64; func @cos(value: f32) -> f32; func @cos(value: f64) -> f64; func @tan(value: f32) -> f32; func @tan(value: f64) -> f64; func @sinh(value: f32) -> f32; func @sinh(value: f64) -> f64; func @cosh(value: f32) -> f32; func @cosh(value: f64) -> f64; func @tanh(value: f32) -> f32; func @tanh(value: f64) -> f64; func @asin(value: f32) -> f32; func @asin(value: f64) -> f64; func @acos(value: f32) -> f32; func @acos(value: f64) -> f64; func @atan(value: f32) -> f32; func @atan(value: f64) -> f64; func @log(value: f32) -> f32; func @log(value: f64) -> f64; func @log2(value: f32) -> f32; func @log2(value: f64) -> f64; func @log10(value: f32) -> f32; func @log10(value: f64) -> f64; func @floor(value: f32) -> f32; func @floor(value: f64) -> f64; func @ceil(value: f32) -> f32; func @ceil(value: f64) -> f64; func @trunc(value: f32) -> f32; func @trunc(value: f64) -> f64; func @round(value: f32) -> f32; func @round(value: f64) -> f64; func @abs(value: s8) -> s8; func @abs(value: s16) -> s16; func @abs(value: s32) -> s32; func @abs(value: s64) -> s64; func @abs(value: f32) -> f32; func @abs(value: f64) -> f64; func @exp(value: f32) -> f32; func @exp(value: f64) -> f64; func @exp2(value: f32) -> f32; func @exp2(value: f64) -> f64; func @pow(value1: f32, value2: f32) -> f32; func @pow(value1: f64, value2: f64) -> f64; func @min(value1: s8, value2: s8) -> s8; func @min(value1: s16, value2: s16) -> s16; func @min(value1: s32, value2: s32) -> s32; func @min(value1: s64, value2: s64) -> s64; func @min(value1: u8, value2: u8) -> u8; func @min(value1: u16, value2: u16) -> u16; func @min(value1: u32, value2: u32) -> u32; func @min(value1: u64, value2: u64) -> u64; func @min(value1: f32, value2: f32) -> f32; func @min(value1: f64, value2: f64) -> f64; func @max(value1: s8, value2: s8) -> s8; func @max(value1: s16, value2: s16) -> s16; func @max(value1: s32, value2: s32) -> s32; func @max(value1: s64, value2: s64) -> s64; func @max(value1: u8, value2: u8) -> u8; func @max(value1: u16, value2: u16) -> u16; func @max(value1: u32, value2: u32) -> u32; func @max(value1: u64, value2: u64) -> u64; func @max(value1: f32, value2: f32) -> f32; func @max(value1: f64, value2: f64) -> f64; func @bitcountnz(value: u8) -> u8; func @bitcountnz(value: u16) -> u16; func @bitcountnz(value: u32) -> u32; func @bitcountnz(value: u64) -> u64; func @bitcounttz(value: u8) -> u8; func @bitcounttz(value: u16) -> u16; func @bitcounttz(value: u32) -> u32; func @bitcounttz(value: u64) -> u64; func @bitcountlz(value: u8) -> u8; func @bitcountlz(value: u16) -> u16; func @bitcountlz(value: u32) -> u32; func @bitcountlz(value: u64) -> u64; func @byteswap(value: u16) -> u16; func @byteswap(value: u32) -> u32; func @byteswap(value: u64) -> u64; func @rol(value: u8, num: u8) -> u8; func @rol(value: u16, num: u16) -> u16; func @rol(value: u32, num: u32) -> u32; func @rol(value: u64, num: u64) -> u64; func @ror(value: u8, num: u8) -> u8; func @ror(value: u16, num: u16) -> u16; func @ror(value: u32, num: u32) -> u32; func @ror(value: u64, num: u64) -> u64; func @muladd(val1: f32, val2: f32, val3: f32) -> f32; func @muladd(val1: f64, val2: f64, val3: f64) -> f64;

Init

@init Intrinsic

Reinitializes a variable or memory region to either its type default value or a provided custom value (tuple for aggregates).

Scalars — Default Initialization

Reinitialize a single variable to its default value.

#test { var x = 666 @init(x) // Reset variable 'x' to its default (0) @assert(x == 0) }

Scalars — Initialization with a Specific Value

Reinitialize a variable with a custom value instead of its default.

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

Arrays — Count-Based Default Initialization

Reinitialize a specified number of elements in an array or memory block.

#test { var x = [1, 2] @init(&x, 2) // Reset first 2 elements to their default (0) @assert(x[0] == 0) @assert(x[1] == 0) x[0] = 1 x[1] = 2 @init(x) // Reset the entire array to default values @assert(x[0] == 0) @assert(x[1] == 0) }

Arrays — Initialization with a Specific Value

Initialize all targeted elements in an array to a given value.

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

Structs — Reset to Declared Defaults

Reinitialize a struct instance to its declared default field values.

#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 struct fields to their declared defaults @assert(rgb.r == 1) @assert(rgb.g == 2) @assert(rgb.b == 3) }

Structs — Initialization with Specific Field Values

Reinitialize a struct instance with custom field values.

#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) // Assign new custom values to all struct fields @assert(rgb.r == 5) @assert(rgb.g == 6) @assert(rgb.b == 7) }

Arrays of Structs — Bulk Initialization and Reinitialization

Reinitialize all elements of an array of structs with specified field values.

#test { struct RGB { r = 1, g = 2, b = 3 } var rgb: [4] RGB @init(&rgb, 4)(5, 6, 7) // Initialize all 4 elements with (5, 6, 7) @assert(rgb[3].r == 5) @assert(rgb[3].g == 6) @assert(rgb[3].b == 7) @init(rgb)(50, 60, 70) // Reinitialize entire 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 @drop(&rgb, 4) // If RGB defines opDrop, it will be invoked here for each element @init(&rgb, 4)(5, 6, 7) // Reinitialize array elements 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 { { // Generic function where 'T' is the type parameter. func(var T) myFunc(val: T) => 2 * val @assert(myFunc's32(2) == 4) // Explicitly using 's32' as the generic type. @assert(myFunc'f32(2.0) == 4.0) // Explicitly using 'f32' as the generic type. } { // Declaring a generic function 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. } { // Generic function with a default type parameter. 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'. } { // Function with multiple generic parameters. func(K, V) myFunc(key: K, value: V) => value @assert(myFunc(2's32, "value") == "value") // K and V inferred from arguments. @assert(myFunc'(s32, string)(2, "value") == "value") // K and V explicitly specified. @assert(myFunc(2's32, true) == true) // Type deduction 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) // Type 'T' deduced as 's32'. @assert(myFunc(2.0'f32) == 4.0) // Type 'T' deduced as 'f32'. }

Using Constants as Generic Parameters

In addition to types, constants can also be used as generic parameters.

In this example, N is a constant of type s32.

#test { func(const N: s32) myFunc() => @assert(N == 10) myFunc'10() // Calls the function with constant value 10. }

const can be omitted when declaring constants, as an identifier followed by a type is treated as a constant.

#test { func(N: s32) myFunc() => @assert(N == 10) myFunc'10() // Equivalent to using 'const'. }

You can also assign a default value to a constant parameter.

#test { func(N: s32 = 10) myFunc() => @assert(N == 10) myFunc() // Uses the default constant value 10. }

If you declare the constant using const, the type can be omitted, and it will be deduced from the assigned value.

#test { func(const N = 10) myFunc() => @assert(N == 10) myFunc() // Type of N deduced from literal 10. }

Mixing Types and Constants

You can mix type parameters and constant parameters within the same generic function.

#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) // 's32' type and constant 10 used. @assert(call(100) == 1000) // Same type and constant reused. } { // Example using multiple constant parameters. func(T: s32, N: s32) myFunc() => T * N @assert(myFunc'(5, 10)() == 50) // Function called with two s32 constants. } { // Multiple type parameters with default values. func(T = s32, V = s32) myFunc(x: T, y: V) => x * y @assert(myFunc(1's32, 2'f32) == 2.0) // Mixed s32 and f32, type deduced. @assert(myFunc(1's32, 2's32) == 2) // Both parameters use s32. } }

Structs

Generic Structs

Structs in Swag can also be made generic, allowing them to operate with different types and constants.

#test { { // Generic struct example where 'T' represents a type parameter. struct(T) Struct { val: T } let x: Struct's32 @assert(#typeof(x.val) == s32) // The field 'val' has type 's32'. let x1: Struct'f32 @assert(#typeof(x1.val) == f32) // The field 'val' has type 'f32'. } { // Generic struct with both a type and a constant parameter. struct(T, N: s32) Struct { val: [N] T // Array of 'N' elements of type 'T'. } let x: Struct'(bool, 10) @assert(#typeof(x.val) == #type [10] bool) // 'val' is an array of 10 booleans. } }

Where Constraints

'Single Evaluation'

The where clause in Swag applies constraints on function invocations, ensuring they can only be called when specific conditions are met. This is especially useful in generic functions where you want to restrict permissible types or values.

When the where expression evaluates to false, the function is not considered during the call; if no alternative overloads match, the compiler raises an error. The where expression is evaluated only once, usually during function instantiation, making it ideal for stable constraints on generic parameters.

#test { // Validate the type: only 's32' or 's64' are accepted for T. func(T) sum(x: T...)->T where T == s32 or T == s64 { var total = 0'T foreach it in x do total += it return total } // Valid calls: T is 's32' or 's64'. let res1 = sum's32(1, 2) @assert(res1 == 3) let res2 = sum's64(10, 20) @assert(res2 == 30) // The following would be an error: 'f32' is not accepted. // var res3 = sum'f32(1, 2) }

'Generic Specialization'

The where clause supports specialized implementations of generic functions. You can provide distinct implementations based on type or value to improve clarity and efficiency.

#test { // Specialization for 's32'. #[Swag.Overload] func(T) isNull(x: T)->bool where T == s32 { return x == 0 } // Specialization for 'f32' and 'f64'. #[Swag.Overload] func(T) isNull(x: T)->bool where T == f32 or T == f64 { return @abs(x) < 0.01 } @assert(isNull(0's32)) @assert(isNull(0.001'f32)) }

'Block-based where' Clause

The where clause can be a block returning a bool, which enables more complex compile-time checks.

#test { // Function accepts only T in {'s32', 's64'} via a block-based condition. func(T) sum(x: T...)->T where { if #typeof(T) == s32 or #typeof(T) == s64 do return true return false } { var total = 0'T foreach it in x do total += it return total } }

'Custom Compile-time Errors'

Using the @compilererror intrinsic, you can emit custom compile-time errors when where conditions fail. This guides users with clear diagnostics.

#test { func(T) sum(x, y: T)->T where { if T == s32 or T == s64 do return true @compilererror('Invalid type ' ++ #stringof(T), #location(T)) return false } { return x + y } // This would trigger a compile-time error: 'f32' is not valid here. // var x = sum'f32(1, 2) }

'Generic Structs' with where

The where clause also applies to generic structs. If the condition is not met, an error is emitted immediately (there is no overload resolution for structs).

#test { // Point constrained to floating types 'f32' or 'f64'. struct(T) Point where T == f32 or T == f64 { x, y: T } // Valid instantiation with 'f32'. var v: Point'f32 // Error: 's32' is not permitted by the struct constraint. // var v: Point's32 }

'Multiple Evaluations' with verify

With verify mode, the constraint is evaluated for each call (rather than once per instantiation). This suits conditions depending on actual arguments, provided they are evaluable at compile time.

#test { { // Division function: ensure 'y' is not zero at compile time when possible. func div(x, y: s32)->s32 verify { // If 'y' is not constexpr, allow the call; otherwise check for zero. if !#isconstexpr(y) do return true if y == 0 do @compilererror("Division by zero", #location(y)) return true } { return x / y } // Valid division. var x1 = div(1, 1) // Compile-time error example: // var x2 = div(1, 0) } { // Overload selected when 'x' is known at compile time. #[Swag.Overload] func first(x: s32)->s32 verify #isconstexpr(x) { return 555 } // Overload selected when 'x' is not known at compile time. #[Swag.Overload] func first(x: s32)->s32 verify !#isconstexpr(x) { return 666 } // Literal: chooses the constexpr version. @assert(first(0) == 555) // Variable: chooses the non-constexpr version. 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 let you annotate code elements (functions, structs, etc.) with metadata. These annotations, defined with the attr keyword, can power code generation, documentation, and reflection. By attaching attributes, you enrich code with extra information usable at compile time and at runtime.

using Swag attr AttributeA() // Simple attribute without parameters

'Attributes with Parameters'

Attributes can accept parameters, similar to functions. Parameters let you customize how the attribute configures the annotated element.

attr AttributeB(x, y: s32, z: string) // Attribute with multiple parameters

'Attributes with Default Values'

Attributes may define default parameter values. When applied, omitted arguments fall back to their defaults.

attr AttributeBA(x: s32, y: string = "string") // Attribute with a defaulted parameter

'Restricting Attribute Usage'

Use the AttrUsage specifier to control where an attribute may be applied (e.g., function-only, struct-only). This improves safety and clarity.

#[AttrUsage(AttributeUsage.Function)] attr AttributeC() // Restricted to function usage

'Applying Attributes'

Apply attributes with the syntax '#[attribute, attribute...]' placed immediately before the code element. Multiple attributes are comma-separated.

#[AttributeA, AttributeB(0, 0, "string")] func function1() { // Function annotated with multiple attributes }

'Multiple Usages'

An attribute can target several element kinds by combining AttrUsage flags with a bitwise OR. This enables reuse across contexts (e.g., functions, structs).

#[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct)] attr AttributeD(x: s32) // 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'

You can inspect attributes via type reflection at runtime. This enables behavior that adapts based on which attributes are present and how they are configured.

#test { let type = #typeof(function2) // Reflect the function type @assert(@countof(type.attributes) == 1) // Exactly one attribute on 'function2' }

Predefined Attributes

Predefined Attributes

This is the list of predefined attributes. All are located in the reserved Swag namespace.

#global skip

Compile-time & Code Generation

#[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct)] attr ConstExpr() // Executable at compile time // Print generated bytecode right after generation (no optimizations yet) #[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct | AttributeUsage.File)] attr PrintGenBc() // Print generated bytecode after bytecode optimizations #[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct | AttributeUsage.File)] attr PrintBc() // Function or variable exists only at compile time #[AttrUsage(AttributeUsage.Function | AttributeUsage.GlobalVariable | AttributeUsage.Constant)] attr Compiler()

Inlining & Call Semantics

#[AttrUsage(AttributeUsage.Function)] attr Inline() // Force inlining // Hint for the 'llvm' backend to never inline #[AttrUsage(AttributeUsage.Function)] attr NoInline() #[AttrUsage(AttributeUsage.Function)] attr CalleeReturn() // 'return' in inlined function returns from caller

Metaprogramming

#[AttrUsage(AttributeUsage.Function)] attr Macro() // Function is a macro #[AttrUsage(AttributeUsage.Function)] attr Mixin() // Function is a mixin

Casting & Overloads & Switches

#[AttrUsage(AttributeUsage.Function)] attr Implicit() // Allow 'opCast' to be implicit #[AttrUsage(AttributeUsage.Function)] attr Complete() // Following switch must be complete #[AttrUsage(AttributeUsage.Function)] attr Overload() // Function can be overloaded

Foreign Interop

#[AttrUsage(AttributeUsage.Function)] attr Foreign(module: string, function: string = '') // Imported function

Result Usage & Deprecation & Generic Control

#[AttrUsage(AttributeUsage.Function | AttributeUsage.Variable)] attr Discardable() // Allow caller to ignore return value #[AttrUsage(AttributeUsage.Function | AttributeUsage.Struct | AttributeUsage.Enum | AttributeUsage.EnumValue)] attr Deprecated(msg: string = null) // Mark definition as deprecated // Force function to not be generic, even inside a generic struct #[AttrUsage(AttributeUsage.Function)] attr NotGeneric()

Memory & Layout (Globals and Structs)

#[AttrUsage(AttributeUsage.GlobalVariable)] attr Tls() // Put global variable in TLS segment #[AttrUsage(AttributeUsage.Struct)] attr Pack(value: u8) // Struct packing information #[AttrUsage(AttributeUsage.Struct)] attr NoCopy() // Struct should never be copied #[AttrUsage(AttributeUsage.Struct)] attr Opaque() // When exporting, do not export struct content // Field member relocation: field offset matches variable 'name' #[AttrUsage(AttributeUsage.StructVariable)] attr Offset(name: string)

Enum Constraints

#[AttrUsage(AttributeUsage.Enum)] attr EnumFlags() // Enum represents a set of flags #[AttrUsage(AttributeUsage.Enum)] attr EnumIndex() // Enum can index arrays without casting #[AttrUsage(AttributeUsage.Enum)] attr NoDuplicate() // Enum values must be unique #[AttrUsage(AttributeUsage.Enum)] attr Incomplete() // Following switch is incomplete

Module Export & Documentation

#[AttrUsage(AttributeUsage.Struct)] attr ExportType(what: string) #[AttrUsage(AttributeUsage.All | AttributeUsage.File)] attr NoDoc() // Do not generate documentation

Safety Controls

Enable or disable safety checks. Examples:

#[Swag.Safety('', false)] // Disable all checks #[Swag.Safety('boundcheck|nan', false)] // Disable 'boundcheck' and 'nan'

Checks:

  • boundcheck -> Out-of-bounds access
  • overflow -> Loss on type conversion
  • math -> Math checks (e.g., negative @sqrt)
  • switch -> Invalid case in #[Swag.Complete] switch
  • unreachable -> Panic on @unreachable
  • any -> Panic if any cast mismatches runtime type
  • bool -> Panic if bool is not true or false
  • nan -> Panic if nan used in float arithmetic
  • sanity -> Per-function sanity check
  • null -> Panic on dereferencing null pointers

If what is null or empty, every option will be affected.

#[AttrUsage(AttributeUsage.All | AttributeUsage.File), AttrMulti] attr Safety(what: string, value: bool)

Optimization Controls

Enable or disable optimizations. Options:

  • bytecode -> Toggle bytecode optimization
  • backend -> Toggle backend machine code optimization (llvm only)

If what is null or empty, every option will be affected.

#[AttrUsage(AttributeUsage.Function | AttributeUsage.File), AttrMulti] attr Optimize(what: string, value: bool)

Arithmetic Behavior

#[AttrUsage(AttributeUsage.All | AttributeUsage.File)] attr CanOverflow(value: bool)

Warning Controls

enum WarningLevel: u8 { Enable // Enable the given warning Disable // Disable the given warning Error // Raise the given warning as an error } // Change behavior of one or more warnings. // Examples: // ``` // #[Swag.Warning('Wrn0006', Swag.WarningLevel.Error)] // #[Swag.Warning('Wrn0002|Wrn0006', Swag.WarningLevel.Disable)] // #global #[Swag.Warning('Wrn0005', Swag.WarningLevel.Enable)] // ``` // You can also configure module-wide behavior in your 'BuildCfg'. #[AttrUsage(AttributeUsage.All | AttributeUsage.File), AttrMulti] attr Warning(what: string, level: WarningLevel)

Matching & Misc

#[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 structured way to organize symbols — functions, variables, and types — within a specific scope. Grouping related symbols under a namespace helps prevent naming conflicts and makes the code more modular and maintainable.

Symbols in a namespace are accessible only through that namespace unless they are explicitly imported or exposed.

// Define a simple namespace 'A' namespace A { // Function 'a' is defined within the namespace 'A'. func a() => 1 }

'Nested Namespaces'

Swag supports nested namespaces, allowing hierarchical organization of symbols. This enables fine-grained structuring of code, which is especially helpful in large projects. In the example below, C is nested inside B, which is nested inside A.

// 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 { // Access functions using their fully qualified namespace paths. @assert(A.a() == 1) // Calls 'a' from namespace 'A' @assert(A.B.C.a() == 2) // Calls 'a' from nested namespace 'A.B.C' }

Using with Namespaces

The using keyword imports symbols from a namespace into the current scope. This eliminates the need to fully qualify symbols, improving readability, especially when dealing with deeply nested namespaces.

using namespace Private { const FileSymbol = 0 // Constant defined within namespace 'Private' } const B = Private.FileSymbol // Access via fully qualified name const C = FileSymbol // Direct access via 'using' directive

Private Scopes

In addition to named namespaces, Swag provides private scopes. A private scope creates a unique, unnamed namespace restricted to the current file. Symbols defined in such a scope are inaccessible outside it, making this useful for isolating internal details.

private { const OtherSymbol = 0 // Constant defined in a file-local private scope } const D = OtherSymbol // Accessible within this file only

'Exporting Symbols'

By default, all symbols in a Swag file are exported to other files within the same module. Using explicit namespaces or private scopes provides protection against accidental symbol conflicts across files.

Defer

defer Statement

The defer statement registers an expression that executes automatically when the current scope is exited. It runs purely at compile time and ensures that cleanup or finalization logic is always performed. This helps maintain clarity and safety in resource management.

#test { var v = 0 defer @assert(v == 1) // Ensures v equals 1 on scope exit. v += 1 // Increment v. // When the scope ends, the deferred expression executes here. }

'Defer in a Block'

A defer can also enclose multiple expressions within a block. This allows you to group operations that should all run when leaving the scope.

#test { var v = 0 defer { v += 10 @assert(v == 15) } v += 5 // Upon scope exit, the defer block executes, ensuring v == 15. }

'Defer with Control Flow'

The deferred expression executes every time a scope exits, even when leaving via return, break, or continue. This guarantees proper cleanup in all control flow paths.

#test { var G = 0 for 10 { defer G += 1 // Increment G at end of each iteration, even if broken. if G == 2 do break // Exiting early still triggers the defer expression. } @assert(G == 3) }

'Defer Execution Order'

When multiple defer statements exist, they execute in reverse declaration order. The most recent defer runs first when the scope ends.

#test { var x = 1 defer @assert(x == 2) // Executes second defer x *= 2 // Executes first // Execution order is reversed for predictable cleanup flow. }

Example: Resource Management

defer is ideal for resource handling — ensuring that allocation and release logic stay close together and cleanup always happens.

#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()) releaseResource(&resource) } if @index == 2 do break // Defer ensures cleanup even on early exit. } @assert(!resource.isResourceCreated()) }

Example: Error Handling

defer ensures reliable cleanup even in functions that may return early due to errors. This pattern creates robust, error-resilient code.

#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) // Always release resource. if !resource.isResourceCreated() { // Early return still triggers defer. return false } // Perform work... return true } let success = performTask() @assert(success) }

Using

using with Enums and Namespaces

The using statement can bring the scope of a namespace, struct, or enum into the current context. This removes the need for full qualification when accessing members. For enums, it simplifies code by avoiding repetitive type prefixes.

#test { enum RGB { R, G, B } @assert(RGB.R == 0) // Fully qualified access. using RGB // Import enum members into the current scope. @assert(G == 1) // 'G' accessible directly without 'RGB.' prefix. }

using with Struct Fields

The using statement can be applied to struct fields, exposing the fields of a nested struct as if they were part of the containing struct. This is useful for composition-like behavior, improving code readability and reducing nesting.

#test { struct Point2 { x, y: s32 } struct Point3 { using base: Point2 // Bring 'Point2' fields into 'Point3' scope. z: s32 } var value: Point3 // Fields from 'base' are directly accessible. value.x = 0 value.y = 0 value.z = 0 @assert(&value.x == &value.base.x) @assert(&value.y == &value.base.y) // Automatic cast: Point3 can be treated as Point2. func set1(ptr: *Point2) { ptr.x, ptr.y = 1 } set1(&value) @assert(value.x == 1) @assert(value.y == 1) @assert(value.base.x == 1) @assert(value.base.y == 1) }

With

with Statement

The with statement reduces repetition by letting you access fields and methods of a variable, struct, or namespace within a scoped block. Inside a with block, the . prefix refers to the selected object, yielding concise, readable code.

struct Point { x, y: s32 } impl Point { mtd setOne() { me.x, me.y = 1 // Set both coordinates to 1 on this Point. } }

with with a Namespace

You can apply with to a namespace to call functions or access constants without fully qualifying names.

namespace NameSpace { func inside0() { // Example namespaced function. } func inside1() { // Another namespaced function. } }

with on a Variable

Use with with a variable to streamline field access without repeating the variable name.

#test { var pt: Point with pt { .x = 1 // Equivalent to 'pt.x = 1' .y = 2 // Equivalent to 'pt.y = 2' } @assert(pt.x == 1) @assert(pt.y == 2) }

with with Function Calls

Inside a with block, you can invoke methods and access fields directly.

#test { var pt: Point with pt { .setOne() // Equivalent to 'pt.setOne()' .y = 2 // Adjust a single field afterward @assert(.x == 1) @assert(.y == 2) } @assert(pt.x == 1) @assert(pt.y == 2) }

with with a Namespace (Usage)

Demonstrates calling namespaced functions via with.

#test { with NameSpace { .inside0() .inside1() } }

with with Variable Declaration

You can declare a variable directly in the with header and work with it immediately inside the block.

#test { with var pt = Point{1, 2} { .x = 10 .y = 20 } @assert(pt.x == 10 and pt.y == 20) } #test { with var pt: Point // Declaration without initializer { .x = 10 .y = 20 } @assert(pt.x == 10 and pt.y == 20) }

with with an Assignment Statement

You can also use with on an assignment to modify the freshly assigned value immediately.

#test { var pt: Point with pt = Point{1, 2} { .x = 10 .y = 20 } @assert(pt.x == 10 and pt.y == 20) }

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 enables powerful metaprogramming patterns for flexible, reusable code.

The primary intrinsics for interacting with types are #typeof and @kindof, which let you introspect and work with types dynamically.

Using #typeof to Inspect Types

The #typeof intrinsic retrieves the type information of an expression. When an expression explicitly represents a type, you can also use the type itself. This is useful for compile-time inspection and validation.

#test { // Basic types via '#typeof' and direct type usage let ptr1 = #typeof(s8) @assert(ptr1.name == "s8") @assert(ptr1 == s8) let ptr2 = #typeof(s16) @assert(ptr2.name == "s16") @assert(ptr2 == s16) let ptr3 = s32 @assert(ptr3.name == "s32") @assert(ptr3 == #typeof(s32)) let ptr4 = s64 @assert(ptr4.name == "s64") @assert(ptr4 == s64) }

Understanding the Result of #typeof

#typeof yields a constant pointer to a Swag.TypeInfo structure (alias of typeinfo). Each Swag type maps to a specific TypeInfo descriptor in the Swag namespace, which is part of the compiler runtime.

Note

You can explore all type descriptors in the runtime documentation on the Swag website.

#test { // Native type -> native typeinfo let ptr = bool @assert(#typeof(ptr) == #typeof(const *Swag.TypeInfoNative)) // Use '#type' to disambiguate when the expression could be parsed as a value let ptr1 = #type [2] s32 @assert(#typeof(ptr1) == #typeof(const *Swag.TypeInfoArray)) @assert(ptr1.name == "[2] s32") // Array literal -> array typeinfo let ptr2 = #typeof([1, 2, 3]) @assert(#typeof(ptr2) == #typeof(const *Swag.TypeInfoArray)) @assert(ptr2.name == "const [3] s32") }

Working with TypeInfo Structures

TypeInfo exposes a kind field identifying the category: native, pointer, array, struct, etc. This is essential when handling types generically.

#test { // 'f64' is a native type let typeOf = f64 @assert(typeOf.kind == Swag.TypeInfoKind.Native) // Compile-time checks of kind using Swag #assert(#typeof(*u8).kind == TypeInfoKind.Pointer) // Pointer #assert(#typeof([1, 2, 3]).kind == TypeInfoKind.Array) // Array #assert(#typeof({1, 2, 3}).kind == TypeInfoKind.Struct) // Struct }

#decltype

#decltype performs the reverse of #typeof/@kindof: it converts a typeinfo back into a compiler type. Use it to materialize a type determined by compile-time information.

#test { // Create a variable whose type is resolved from typeinfo var x: #decltype(#typeof(s32)) #assert(#typeof(x) == s32) }

Using #decltype with Compile-Time Expressions

#decltype can evaluate a constexpr that returns a typeinfo and materialize the corresponding type. This enables dynamic yet type-safe patterns.

#test { // Return a typeinfo based on a constexpr condition #[Swag.ConstExpr] func getType(needAString: bool)->typeinfo { if needAString do return string else do return s32 } // Materialize the chosen type via '#decltype' var x: #decltype(getType(needAString: false)) #assert(#typeof(x) == s32) x = 0 var x1: #decltype(getType(needAString: true)) #assert(#typeof(x1) == string) x1 = "0" }

Error Management and Safety

Error Management

'Errors with throw/try/catch'

A function marked with throw can return an error by using throw followed by an error value. The value is a struct that carries error details.

These are not traditional exceptions. Think of throw as a specialized return that carries an error value.

'Using throw with Custom Errors'

A function that may return an error must be annotated with throw. When an error is raised, it is a structured value (often a custom error struct).

// Custom error extending the runtime base error. struct MyError { using base: Swag.BaseError }

When a function exits early due to throw, the returned value is the default for the function return type (e.g., 0 for integers).

// 'count' returns the length of 'name', or throws if 'name' is null. // On error, it returns the default 'u64' value (0). func count(name: string)->u64 throw { if name == null { throw MyError{"null pointer"} } return @countof(name) }

'Handling Errors with catch and @err'

Use catch at the call site to dismiss a raised error. The result becomes the default value of the callee's return type. Check @err to see what was caught (type any).

func myFunc() { let cpt = catch count("fileName") if @err != null { // '@err' is an 'any' representing the caught error. @assert(@err == MyError) @assert(cpt == 0) @print("An error was raised") return } // No error: continue normally. }

'Error Handling with trycatch'

trycatch dismisses the error and continues, returning the default value. This contains errors within the current function.

func myOtherFunc() { var cpt1 = trycatch count("fileName") // Equivalent pattern using 'catch': var cpt2 = catch count("filename") if @err != null do return }

'Propagating Errors with try'

try halts execution and propagates the error to the caller. The callee must be annotated with throw.

func myFunc1() throw { var cpt = try count("filename") // Propagate on error }

Equivalent to catching and re-throwing the error explicitly.

func myFunc2() throw { var cpt = catch count("filename") if @err != null { throw @err } }

'Forcing Panic with assume'

assume forces a panic if an error occurs, instead of handling or propagating. Useful when errors are not expected.

Note

In release builds, assume may be disabled, which can lead to undefined behavior if an error occurs.

func myFunc3() { var cpt = assume count("filename") // Panic on error }

WARNING: If an error is not caught, Swag will panic at runtime. The top-level caller assumes safe execution and halts the program on unhandled errors.

'Blocks in Error Handling'

Blocks can be used with try, assume, catch, and trycatch to apply the handling strategy to multiple operations. These blocks do not create a new scope.

func myFunc4() throw { // Propagate errors from several calls. try { var cpt0 = count("filename") var cpt1 = count("other filename") } // Panic if any call fails. assume { var cpt2 = count("filename") var cpt3 = count("other filename") } // Dismiss errors and continue (checking '@err' is irrelevant here). catch { var cpt4 = count("filename") var cpt5 = count("other filename") } // Dismiss errors and exit immediately without propagation. trycatch { var cpt6 = count("filename") var cpt7 = count("other filename") } }

'Implicit try'

Inside a function annotated with throw, calls to throwing functions are implicitly treated as try unless overridden.

#test { func mySubFunc2() throw { throw MyError{"error from mySubFunc2"} } func mySubFunc3() throw { throw MyError{"error from mySubFunc3"} } func mySubFunc1() throw { // Implicit 'try' because this function itself is 'throw'. mySubFunc2() // Still allowed to be explicit when desired. try mySubFunc3() } catch mySubFunc1() @assert(@err == MyError) }

'The error struct'

An error is a struct. You can add custom fields (e.g., line/column for a syntax error).

struct SyntaxError { using base: Swag.BaseError line, col: u32 }

WARNING: Ensure references stored in an error (e.g., "string", "any") remain valid for the error lifetime. Prefer heap or a context allocator when needed.

'Using defer for Controlled Cleanup'

defer schedules code to run on function exit (normal return or error). Since throw behaves like return, defer runs consistently in both cases.

You can filter execution with modes: - 'defer #err' -> run only when an error is raised - 'defer #noerr' -> run only when no error is raised - defer -> run always

var g_Defer = 0 func raiseError() throw { throw MyError{"error"} } func testDefer(err: bool) throw { defer #err g_Defer += 1 // Run on error only defer #noerr g_Defer += 2 // Run on success only defer g_Defer += 3 // Run always if err do raiseError() } #test { // Error path: only '#err' and unconditional run. g_Defer = 0 catch testDefer(true) @assert(g_Defer == 4) // 1 + 3 // Success path: only '#noerr' and unconditional run. g_Defer = 0 catch testDefer(false) @assert(g_Defer == 5) // 2 + 3 }

Safety

'Safety Checks in Swag'

Swag provides safety checks that can be enabled at different granularity levels — module, function, or even individual instruction — via #[Swag.Safety].

These checks prevent common programming errors by triggering panics during unsafe operations (overflows, invalid math, out-of-bounds access, etc.).

You can also configure safety checks globally from the build configuration with 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, and disabled in fast-compile and release for performance.

'Overflow Safety'

Example: #[Swag.Safety("overflow", true)] When enabled, Swag panics on arithmetic overflow or when bits are lost during integer conversions.

Operators that can overflow include '+ - * << >>' and compound forms '+= -= = <<= >>='.

#test { var x = 255'u8 // x += 1 // Uncomment to see overflow panic }
Disabling Overflow Safety with #wrap

Use #wrap on the operation if overflow is expected and should not panic.

#test { var x = 255'u8 x += #wrap 1 @assert(x == 0) }
Global Overflow Safety Control

Disable overflow safety checks in a scope with #[Swag.CanOverflow(true)].

#[Swag.CanOverflow(true)] #test { var x = 255'u8 x += 1 @assert(x == 0) }
Promoting Operations to Prevent Overflow

For 8/16-bit operations, use #prom to promote to 32-bit and avoid overflow.

#test { let x = 255'u8 + #prom 1 @assert(x == 256) @assert(#typeof(x) == u32) }

'Information Loss During Casting'

Swag checks casts for potential information loss between integer types.

#test { let x1 = 255'u8 // var y0 = cast(s8) x1 // Would panic: 255 not representable as s8 let y1 = cast #wrap (s8) x1 @assert(y1 == -1) let x2 = -1's8 // var y2 = cast(u8) x2 // Would panic: negative to unsigned let y2 = cast #wrap (u8) x2 @assert(y2 == 255) }
Disabling Overflow Safety Globally

Same as above: #[Swag.CanOverflow(true)] allows overflowing operations.

#[Swag.CanOverflow(true)] #test { var x = 255'u8 x += 255 // -> 254 (wrap) x += 1 // -> 255 x >>= 1 // -> 127 @assert(x == 127) }

'Dynamic Cast Type Safety'

Example: #[Swag.Safety("dyncast", true)] Swag panics if a cast from any to another type is invalid.

#test { let x: any = "1" let y = cast(string) x // var z = cast(s32) x // Would panic: underlying type mismatch // @assert(z == 0) }

Swag also panics if casting from an interface to a pointer-to-struct cannot be performed.

'Array Bounds Checking'

Example: #[Swag.Safety("boundcheck", true)] Swag panics if an index is out of range when dereferencing sized values (arrays, slices, strings).

#test { var x = [0, 1, 2] var idx = 10 // @assert(x[idx] == 1) // Would panic: out-of-bounds }
Safety When Indexing a Slice

Indexing a slice is checked for bounds.

#test { let x: const [..] s32 = [0, 1, 2] var idx = 1 @assert(x[idx] == 1) idx += 9 // @assert(x[idx] == 1) // Would panic: out-of-bounds }
Safety When Slicing a Sized Value

Slice operations are checked for bounds.

#test { var x: const [..] s32 = [0, 1, 2] // var slice = x[1..4] // Would panic: out-of-bounds // @assert(slice[0] == 1) } #test { var x = "string" var idx = 10 // var slice = x[0..idx] // Would panic: out-of-bounds // @assert(slice[0] == 's') }

'Math Safety'

Example: #[Swag.Safety("math", true)] Swag panics for invalid math, such as division by zero or invalid intrinsic arguments.

#test { var x = 1'f32 var y = 0'f32 // var z = x / y // Would panic: division by zero // @print(z) }
Checking Invalid Math Intrinsic Arguments

Swag validates arguments for several math intrinsics and panics if invalid.

#test { // @abs(-128) // Invalid for abs on this target // @log(-2'f32) // Invalid: log of negative // @log2(-2'f32) // Invalid: log2 of negative // @log10(2'f64) // Example: implementation-specific constraints // @sqrt(-2'f32) // Invalid: sqrt of negative // @asin(-2'f32) // Invalid: asin out of range // @acos(2'f32) // Invalid: acos out of range }

'Switch Safety'

Example: #[Swag.Safety("switch", true)] With #[Swag.Complete], Swag panics if a switch does not cover all cases.

#test { enum Color { Red Green Blue } func colorToString(color: Color)->string { // #[Swag.Complete] switch color { case Color.Red: return "Red" case Color.Green: return "Green" } return "" } }

'Boolean Safety'

Example: #[Swag.Safety("bool", true)] Swag panics if a bool is not true (1) or false (0).

#test { var b: u8 = 2 // if b { } // Would panic: invalid boolean value }

'NaN Safety'

Example: #[Swag.Safety("nan", true)] Swag panics if a floating-point NaN participates in an operation, preventing propagation of invalid values.

Compile-time Evaluation

'Compile-Time Execution in Swag'

One of Swag’s most powerful features is its ability to execute everything at compile time. This means Swag can function both as a compiled language and as a scripting language, with the compiler acting as an interpreter.

This flexibility allows developers to perform tasks at compile time that would otherwise require runtime computation — resulting in more efficient, expressive, and versatile programs.

Constexpr

'Compile-Time Function Evaluation with #[Swag.ConstExpr]'

The #[Swag.ConstExpr] attribute marks a function as evaluable during compile time. If all inputs are known at compile time, the compiler can resolve the function result immediately, avoiding runtime computation.

This precomputation improves efficiency and reduces runtime overhead, making such functions ideal for constant logic or static initialization.

// Simple example: always returns true, resolved at compile time. #[Swag.ConstExpr] func isThisDebug() => true // Compile-time conditional block. // Since 'isThisDebug()' is true, the code inside will never compile. #if isThisDebug() == false { #error("this should not be called!") }

'Recursive Compile-Time Evaluation'

Recursive functions can also be marked ConstExpr. Swag evaluates them entirely at compile time, eliminating runtime cost.

#[Swag.ConstExpr] func factorial(x: s32)->s32 { if x == 1 do return 1 return x * factorial(x - 1) } #assert(factorial(4) == 24)

'Compile-Time Constant Expressions'

ConstExpr functions can return fixed values that the compiler resolves immediately during compilation.

#[Swag.ConstExpr] func getMagicNumber()->s32 { return 42 } #assert(getMagicNumber() == 42)

'Compile-Time Conditional Logic'

Logic such as even/odd checks can be done at compile time.

#[Swag.ConstExpr] func isEven(x: s32)->bool { return x % 2 == 0 } #if isEven(4) == false { #error("4 should be even!") }

'Compile-Time Slice Operations'

ConstExpr functions can iterate and compute over constant arrays.

#[Swag.ConstExpr] func arraySum(arr: const [..] s32)->s32 { var sum = 0 foreach val in arr do sum += val return sum } #assert(arraySum([1, 2, 3, 4, 5]) == 15)

'Compile-Time Fibonacci Sequence'

Recursive computation of Fibonacci numbers at compile time.

#[Swag.ConstExpr] func fibonacci(n: s32)->s32 { if n <= 1 do return n return fibonacci(n - 1) + fibonacci(n - 2) } #assert(fibonacci(5) == 5)

'Compile-Time Bitwise Operations'

Bitwise operations can be resolved at compile time too.

#[Swag.ConstExpr] func isBitSet(num: s32, bit: s32)->bool { return (num & (1 << bit)) != 0 } #assert(isBitSet(8, 3) == true)

Run

'Force Compile-Time Execution with #run'

The #run directive allows any function to execute at compile time, even if it’s not marked with #[Swag.ConstExpr]. This means you can trigger compile-time execution of regular, external, or system functions as part of your program.

// Regular runtime function func isThisRelease() => true // Forcing compile-time evaluation with '#run' #if #run isThisRelease() == false { #error("this should not be called!") }

Any function — including system or user-defined ones — can be executed at compile time using #run.

// Example: sum without 'ConstExpr' func sum(values: s32...)->s32 { var result = 0's32 foreach v in values do result += v return result } // Force execution at compile time const SumValue = #run sum(1, 2, 3, 4) + 10 #assert(SumValue == 20)

#run Block

#run blocks execute arbitrary logic at compile time. They are useful for initializing globals or precomputing data before runtime.

Execution order between #run blocks is undefined, so avoid relying on order.

// Global array initialized at compile time var G: [5] f32 = undefined #run { var value = 1'f32 for i in @countof(G) { G[i] = value value *= 2 } } // Validate precomputed results #test { @assert(G[0] == 1) @assert(G[1] == 2) @assert(G[2] == 4) @assert(G[3] == 8) @assert(G[4] == 16) }

Swag can act like a scripting language: if a project only contains #run blocks, it behaves like a compile-time script.

#run Expression

#run can also be used as an expression block. Its return type is inferred from the return statement inside the block.

#test { const Value = #run { var result: f32 for 10 do result += 1 return result } #assert(Value == 10.0) }

Example: initializing a static array at compile time.

#test { const N = 4 const PowerOfTwo: [N] s32 = #run { var arr: [N] s32 for i in arr do arr[i] = 1 << cast(u32) i return arr } #assert(PowerOfTwo[0] == 1) #assert(PowerOfTwo[1] == 2) #assert(PowerOfTwo[2] == 4) #assert(PowerOfTwo[3] == 8) }

Example: compile-time string construction.

#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") }

Example: initializing a struct in a #run block.

#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

Complex structs that implement opCount and opSlice can be converted to static arrays at compile time. The compiler calls opCount for array size, opSlice for initialization, and opDrop after conversion if present.

Compiler Instructions

#assert

The #assert directive performs a static assertion during compilation. If the condition is false, compilation fails with an error message. Use it to enforce compile-time invariants and validate assumptions.

#assert(true) // Always passes; no error.

#defined(SYMBOL)

#defined(SYMBOL) checks at compile time whether a symbol exists in the current context. It returns true if defined, false otherwise. Useful for conditional compilation before referencing variables, constants, or functions.

#assert(!#defined(DOES_NOT_EXISTS)) // Ensure the symbol is not defined. #assert(#defined(Global)) // Confirms 'Global' is defined. var Global = 0 // Define a global variable 'Global'.

#if/#elif/#else

Static conditional compilation. Expressions are evaluated at compile time to include or exclude code based on constants or conditions.

const DEBUG = 1 const RELEASE = 0 #if DEBUG { // Compiled because DEBUG == 1. } #elif RELEASE { // Would compile if RELEASE were true and DEBUG were false. } #else { // Compiled if neither DEBUG nor RELEASE is true. }

#error/#warning

#error raises a compile-time error with a custom message. #warning emits a compile-time warning without stopping compilation. Useful for enforcing checks and surfacing build-time information.

#if false { #error("this is an error") // Compile-time error (if reached). #warning("this is a warning") // Compile-time warning (if reached). }

#global

Place #global at the top of a source file to apply global settings or attributes across the entire file. Controls compilation and symbol visibility.

Examples (write these as top-level directives): #global skip // Skip file content (file must still be valid). #global public // All symbols become public. #global internal // All symbols are internal to the module. #global namespace Toto // Place all symbols in namespace Toto. #global #if DEBUG == true // Conditional compilation for the file. #global #[Swag.Safety(, true)] // Apply an attribute to all declarations. #global export // Export the file; copied to the public folder.

#foreignlib

Link against an external library during compilation to use its functions, variables, or resources. Provide the library name as a string.

Example: #foreignlib("windows.lib") This links the program with "windows.lib" and enables Windows API usage.

Code Inspection

#message Function

The #message function in Swag is a special compiler hook invoked when certain build events occur. It allows custom actions or checks to run at specific stages of compilation.

The function receives a mask defining which events trigger it, such as function typing, global variable processing, or semantic pass completion.

Function Message Mask

Using Swag.CompilerMsgMask.SemFunctions, #message runs whenever a function in the module is fully typed (its type info is available). Inside the function, @compiler.getMessage() retrieves information about the event.

#message(Swag.CompilerMsgMask.SemFunctions) { let itf = @compiler let msg = itf.getMessage() // Since the message is about a function, cast its type. let typeFunc = cast(const *Swag.TypeInfoFunc) msg.type let nameFunc = msg.name // Count functions starting with "XX" if @countof(nameFunc) > 2 and nameFunc[0] == 'X' and nameFunc[1] == 'X' do G += 1 } // Global counter for functions starting with "XX" var G = 0 func XXTestFunc1() {} func XXTestFunc2() {} func XXTestFunc3() {}

Semantic Pass Completion

After the semantic pass (once all functions are parsed and typed), #message(Swag.CompilerMsgMask.PassAfterSemantic) is triggered. Ideal for whole-module checks or summary actions.

#message(Swag.CompilerMsgMask.PassAfterSemantic) { @assert(G == 3) }

Global Variables Message Mask

Swag.CompilerMsgMask.SemGlobals triggers for each global variable during semantic analysis. This can be used to validate or analyze globals.

#message(Swag.CompilerMsgMask.SemGlobals) { let itf = @compiler var msg = itf.getMessage() // Example placeholder for analyzing global variable properties }

Global Types Message Mask

Swag.CompilerMsgMask.SemTypes triggers for each global type (struct, enum, interface) encountered by the compiler. This enables compile-time inspection or analysis of type information.

#message(Swag.CompilerMsgMask.SemTypes) { let itf = @compiler var msg = itf.getMessage() // Example placeholder for analyzing global type attributes }

Meta Programming

'Compile-Time Source Code Generation'

Swag allows you to construct and inject source code dynamically at compile time. The code is provided as a string containing valid Swag syntax, which the compiler then parses and integrates into the final program.

This feature enables powerful metaprogramming patterns — you can generate, modify, or extend code during compilation, reducing duplication and creating highly flexible, adaptive programs.

Ast

#ast Block

The #ast block lets you dynamically generate and inject Swag code at compile time. It produces a string that the compiler treats as if it were written directly in the source. This allows for dynamic, context-dependent code generation.

Basic #ast Usage

A #ast block can return a simple string expression representing Swag code.

#test { #ast "var x = 666" @assert(x == 666) }

#ast Block with return

A #ast block can include logic and must return a string to compile.

#test { var cpt = 2 #ast { const INC = 5 return "cpt += " ++ INC } @assert(cpt == 7) }

#ast for Structs and Enums

You can use #ast inside struct or enum definitions to generate members dynamically.

#test { struct MyStruct { #ast { return "x, y: s32 = 666" } } let v: MyStruct @assert(v.x == 666) @assert(v.y == 666) }

#ast with Generics

#ast works with generics for flexible and reusable code generation.

#test { struct(T) MyStruct { #ast { return "x, y: " ++ #typeof(T).name } z: string } let v: MyStruct'bool #assert(#typeof(v.x) == bool) #assert(#typeof(v.y) == bool) #assert(#typeof(v.z) == string) let v1: MyStruct'f64 #assert(#typeof(v1.x) == f64) #assert(#typeof(v1.y) == f64) #assert(#typeof(v1.z) == string) }

Constructing Strings in #ast

#ast must return a string. You can construct it dynamically, e.g. by using a buffer.

#test { #[Swag.Compiler] func append(buf: [*] u8, val: string) { var len = 0 while buf[len] do len += 1 @memcpy(buf + len, @dataof(val), cast(u64) @countof(val) + 1) } struct Vector3 { #ast { var buf: [256] u8 append(buf, "x: f32 = 1\n") append(buf, "y: f32 = 2\n") append(buf, "z: f32 = 3\n") return cast(string) buf } } let v: Vector3 @assert(v.x == 1) @assert(v.y == 2) @assert(v.z == 3) }

Real-World Example

Example from Std.Core — dynamically generates a struct with all fields of another struct replaced by bools.

struct(T) IsSet { #ast { var str = StrConv.StringBuilder{} let typeof = #typeof(T) foreach f in typeof.fields: str.appendFormat("%: bool\n", f.name) return str.toString() } }

#ast at Global Scope

#ast can generate global declarations dynamically as well.

#ast { const value = 666 return "const myGeneratedConst = " ++ value } #assert(myGeneratedConst == 666)

Compiler Interface

compileString() in @compiler

The compileString() function, available through the @compiler interface, allows you to inject and compile dynamically generated Swag source code during compilation. It is typically used inside a #message block to generate code after specific compiler events.

Example — OpenGL Function Registration

In this example (from Std.Ogl), compileString() is used within a #message hook to find all functions tagged with a custom attribute Ogl.Extension and automatically generate extension initialization code.

First, define a user attribute for marking OpenGL extension functions.

#[AttrUsage(AttributeUsage.Function)] attr Extension()

Then, apply it to placeholder OpenGL functions.

#[Extension, Swag.PlaceHolder] { 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) }

Track all functions with that attribute.

struct OneFunc { type: typeinfo name: string } #[Compiler] var g_Functions: Array'OneFunc

Register all tagged functions as they are typed.

#message(CompilerMsgMask.SemFunctions) { let itf = @compiler var msg = itf.getMessage() if !Reflection.hasAttribute(msg.type, Extension) return g_Functions.add({msg.type, msg.name}) }

Once semantic analysis finishes, generate code for all registered functions.

#message(CompilerMsgMask.PassAfterSemantic) { var builderVars: StringBuilder var builderInit: StringBuilder builderInit.appendString("public func glInitExtensions()\n{\n") foreach e in g_Functions { let typeFunc = cast(const *TypeInfoFunc) e.type builderVars.appendFormat("var ext_%: %\n", e.name, typeFunc.name) 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") builderInit.appendFormat( "\text_% = cast(%) getExtensionAddress(@dataof(\"%\"))\n", e.name, typeFunc.name, e.name) } 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 <html> is a verbatim paragraph where every blanks and end of lines are respected. The paragraph will be generated as is without any markdown change.

// <html> // Even... // // ...empty lines are preserved. // // You end that kind of paragraph with '</html>' alone on its line. // Note that **everything** is not bold, put printed 'as it is'. // </html>
Result

Even... ...empty lines are preserved. You end that kind of paragraph with '' alone on its line. Note that **everything** is not bold, put printed 'as it is'.

Horizontal Lines

Use --- at the start of a line to create an HTML horizontal line (<br>)

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

Documentation Generation in Swag

When using Swag.DocKind.Api mode, Swag collects all public definitions to automatically generate API documentation.

A good example of this is the documentation for Std.Core.

Module-Level Documentation

The main module documentation should be placed at the top of the module.swg file.

// This is the main module documentation. #dependencies { }

Element-Level Documentation

Comments placed immediately before a func, struct, or enum become their documentation entries.

The first paragraph acts as the short description that appears in summaries.

If the first line ends with a dot ., it marks the end of the short description.

// This first paragraph is the short description of function 'test1'. // // This second paragraph serves as the long description. func test1() {} // This is the short description of 'test'. // As the previous line ends with '.', the following text becomes // the long description, even without a blank line. func test() {}

Inline Comments for Constants and Enums

Constants and enum values can include documentation comments on the same 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 reference other documented elements using [[name]] or fully qualified forms like [[name1.name2]].

// This is a function with a 'value' parameter. func one(value: s32) {} // This is a reference to [[one]]. func two() {}

Hiding from Documentation

Use the #[Swag.NoDoc] attribute to exclude an element from the generated documentation.

// The function 'three' will be ignored by the documentation generator. #[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 12-10-2025 with swag 0.45.0