.. _language-syntax: Syntax of CSL ============= This document describes the basic structures of the CSL language. Type system overview -------------------- The basic types of CSL are: * void type (``void``) * signed integers (``i8``, ``i16``, ``i32``, ``i64```) * unsigned integers (``u8``, ``u16``, ``u32``, ``u64```) * floating point numbers (``f16``, ``f32``) Arrays types are spelled ``[num_elements] base_type``, for example: ``[3] i16``. Array literals are specified by an array type followed by a list of values, for example: ``[3]i16 {1, 2, 3}`` For a detailed introduction to the type system of CSL, see :ref:`language-types`. .. _variables: Variables --------- Variable declarations are composed of a mutability specifier, a name, a type and an initializer: .. code-block:: csl const ten_i : i16 = 10; var ten_f : f16 = 10.0; param ten_d : f32 = 10.0; A ``const`` or ``param`` variable cannot have its value changed after it has been initialized, whereas a ``var`` variable has no such restriction. The initializer expression is: * Mandatory for ``const`` variables. * Optional for ``var`` variables. * Optional for ``param`` variables. If one is not provided, the ``param`` must be initialized through the module import system. See :ref:`language-modules`. The type expression is optional. If one is not provided, the initializer expression is mandatory and it is used to deduce the type of the variable: .. code-block:: csl const ten_a : i16 = 10; const ten_b = ten_a; // ten_b is also i16. param my_param1; // ok, the initializer is provided later. Variable declarations may optionally have an alignment requirement: .. code-block:: csl const aligned_var1 : i16 align(32) = 10; const aligned_var2 align(64) = ten_a; The memory address of the corresponding variable is guaranteed to have *at least* the specified alignment. Alignment is specified as a number of bytes and must always be a power of two. Global variables can be used before their declaration. For example, the following is legal: .. code-block:: csl fn my_fn(x: f16) void { my_global = x; } var my_global : f16; Global variable declarations may also optionally specify the name of the link section: .. code-block:: csl var global_var1 : i16 linksection(".mySection") = 10; By specifying the link section name ``.mySection``, the global variable gets placed into a separate object file section named ``.mySection``, instead of being placed into the object file section with the rest of the global variables. The ``linksection`` attribute can be used together with the compiler flag ``--link-section-start-address-bytes`` to place global variables at particular memory addresses: .. code-block:: csl var section1: u16 linksection(".mySection1") = 0xabcd; var section2: u16 linksection(".mySection2") = 0x1234; // $ cslc-driver ... \ // --link-section-start-address-bytes=".mySection1:40960,.mySection2:40980" In the example above, the variable ``section1`` is placed at the memory address 40960 (bytes), and ``section2`` is placed at 40980. Global variable declarations may also optionally specify the name of the ELF symbol corresponding to the variable: .. code-block:: csl var global_var : i16 linkname("different_name") = 10; In this example, the global variable known as ``global_var`` within CSL gets assigned the name ``different_name`` in the compiled object file. This can be useful to control the name of symbols that are intended to be referenced by other object files as external data. Any comptime expression evaluating to a value of type ``comptime_string`` may be used for ``linkname``. Global variable declarations may optionally specify a storage class (either ``export`` or ``extern``). If a variable is declared ``export``, it is made accessible to other separately-compiled objects, and is guaranteed not to be eliminated from the compiled object. If a variable is declared ``extern``, it is assumed that its definition will be supplied by another object that will later be linked with the object we are compiling. An ``extern`` declaration must _not_ initialize the variable. Variables with the ``export`` or ``extern`` storage classes must have an *export-compatible* type. See :ref:'language-storage-classes' for details. .. code-block:: csl // Variable 'x' will be available to other objects that are linked with // this program. export var x: i16 = 12; // We expect that variable 'y' will be provided by another object that is // to be linked with this program. extern var y: i16; // Variable 'foo' will be available under the name 'alias_for_foo' to other // objects that are linked with this program. export var foo: i16 linkname("alias_for_foo") = 42; // Variable 'alias_for_bar' will be aliased to the a variable 'bar' provided // by another object that is to be linked with this program. extern var alias_for_bar: i16 linkname("bar"); Pointers -------- To obtain a pointer to a variable, the address-of operator ``&`` is used: .. code-block:: csl var x = [2]i16 {0, 1}; var ptr = &x; // ptr is a *[2]i16 const y = [2]i16 {0, 1}; const const_ptr = &y; // const_ptr is a *const[2]i16 Only variables are addressable, as such it is illegal to obtain the address of a temporary: .. code-block:: csl const x = [2]i16 {0,0}; const ok_ptr = &x[1]; const bad_ptr = &(([2]i16 {0,0})[1]); // compile-time error. To dereference a pointer, the dereference operator ``.*`` is used: .. code-block:: csl var x = [2]i16 {0, 1}; var ptr_to_x = &x; // ptr is a *[2]i16 var copy_of_x = ptr_to_x.*; // copy_of_x is a [2]i16 var element_of_x = ptr_to_x.*[1]; // element_of_x is an i16 Functions --------- Function definitions require a ``fn`` or ``task`` keyword, a name, an optional sequence of parameters, a return type and a function body: .. code-block:: csl fn foo(arg : i16) i32 { ... } task my_task(arg : i16) void { ... } All function parameters are implicitly ``const`` variables. It is unspecified whether function parameters are passed by value or by reference. If it is necessary to modify a function argument, the function parameter must be declared with a pointer type: .. code-block:: csl fn foo(arg : *i16) void { arg.* = 42; } fn bar() void { var x : i16 = 0; foo(&x); // x is now 42. } The type of a function parameter may be specified with the keyword ``anytype``. In this case, the compiler will create a specialized copy of the function based on the *type* of the corresponding argument used at the call site. This is similar to ``typename`` templates in C++. .. code-block:: csl /// Computes base ^ exp fn pow(base : anytype, exp : @type_of(base)) @type_of(base) { const base_type = @type_of(base); if (@is_same_type(base_type, i16)) { // ... integer implementation ... } if (@is_same_type(base_type, f16)) { // ... float implementation ... } return @as(base_type, 0); } task t() void { const v1 : i16 = ...; pow(v1, 6); // specialized for `i16`. const v2 : f16 = ...; pow(v2, 6.0); // specialized for `f16`. } Function parameters can optionally be marked with the ``comptime`` keyword (see :ref:`language-comptime`). In this case, the compiler will create a specialized copy of the function based on the *value* of the corresponding argument at the call site. The argument must be comptime-known. This is similar to non-type template parameters in C++. .. code-block:: csl /// This function is specialized for each value of base_type. fn copy(size : i16, comptime base_type : type, dest : [*]base_type, src : [*]base_type) void { for (@range(i16, size)) |idx| { dest[idx] = src[idx]; } } task t() void { var src = @constants([10]i16, 42); var dest : [10]i16; copy(10, i16, &src, &dest); // specialized for i16. } Function definitions may also optionally specify the name of the ELF symbol corresponding to the function: .. code-block:: csl fn foo () linkname("bar") void { ... } In this example, the function known as ``foo`` within CSL gets assigned the name ``bar`` in the compiled object file. This can be useful to control the name of functions that are intended to be called by other object files as ``extern`` functions. Any comptime expression evaluating to a value of type ``comptime_string`` may be used for ``linkname``. Function declarations may optionally specify a storage class (either ``export`` or ``extern``). If a function is declared ``export``, it is made accessible to other separately-compiled objects, and its definition is guaranteed not to be eliminated from the compiled object. If a function is declared ``extern``, it is assumed that its definition will be supplied by another object that will later be linked with the object we are compiling. An ``extern`` function declaration must _not_ contain a function body. Functions with the ``export`` or ``extern`` storage classes must have an *export-compatible* type. See :ref:'language-storage-classes' for details. .. code-block:: csl // Function 'f' will be available to other objects that are linked with // this program. export fn f(x: i16, y: i16) { return x+y; } // We expect that function 'g' will be provided by another object that is // to be linked with this program. extern fn g(f16, f16) f16; // Function 'foo' will be available under the name 'alias_for_foo' to other // objects that are linked with this program. export fn foo(x: *i16) i16 linkname("alias_for_foo") { return x.*; } // Function 'alias_for_bar' will be aliased to the a function 'bar' provided // by another object that is to be linked with this program. extern fn alias_for_bar(*f16) f16 linkname("bar"); Direct and Indirect Function Calls ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Functions can be called directly by name or indirectly through function pointers. For example: .. code-block:: csl fn foo(a: i16, b: f32) f32 { ... } var foo_ptr: *const fn(i16,f32)f32 = foo; task main() void { foo(42, 3.14); // Direct function call foo_ptr(67, 42.0); // Indirect function call } The function value ``foo`` in the example above is implicitly coerced to the requested function pointer type. Note however that function values can only be coerced to ``const`` function pointers as shown in the example above. It is also possible to take the address of a function symbol using the address-of operator ``&`` as shown in the example below: .. code-block:: csl fn foo() void { ... } var foo_ptr: *const fn()void = &foo; task main() void { foo_ptr(); // Indirect function call } Taking the address of a function using the ``&`` operator is semantically equivalent to the implicit coercion of a function value to a ``const`` function pointer type. This means that the resulting address will always be a ``const`` pointer as well. Statements ---------- If-statement ~~~~~~~~~~~~ If-statements have the following syntax: .. code-block:: csl if (condition) { // ... } else { // ... } If ``condition`` is known at compile-time, the branch not-taken is not semantically checked by the compiler, but it must still be syntactically valid. The ``else`` clause is optional. It is possible to combine an ``else`` clause with another if-statement: .. code-block:: csl if (condition) { // ... } else if { // ... } else { // ... } For-statement ~~~~~~~~~~~~~ A for-statement iterates over the elements of an array or range: .. code-block:: csl for (my_array) |element| { // ... } for (@range(i32, 0, 2, 100)) |element| { // ... } Inside the loop body, the variable ``element`` acts as a ``const`` declaration whose value is the element that is currently being iterated on. For-statements may specify a ``const`` declaration for the index of the element being iterated on: .. code-block:: csl for (my_array) |element, index| { // ... } A ``break`` statement may be used to end the loop: .. code-block:: csl for (my_array) |element, idx| { // ... if (condition) { break; } } A ``continue`` statement may be used to end the current iteration of the loop: .. code-block:: csl for (my_array) |element, idx| { // ... if (condition) { continue; } } While-statement ~~~~~~~~~~~~~~~ While-statements have the following syntax: .. code-block:: csl while (condition) { // ... } ``continue`` or ``break`` statements may be used inside the body of a while-statement. A while-statement may optionally specify an assignment expression: .. code-block:: csl while (condition) : (i += 3) { // ... } The assignment expression executes at the end of each loop iteration, including iterations finished with a ``continue`` statement. Switch-statement ~~~~~~~~~~~~~~~~ Switch-statements have the following syntax: .. code-block:: csl switch (input) { case_values1 => branch_expr1, case_values2 => branch_expr2, ... else => else_expr } ``input`` can be an expression of a fixed-width integer type (i.e., ``comptime_int`` is not allowed) or of any enum type. The body of the switch statement consists of 1 or more comma-separated branches where each branch consists of 2 parts: the ``case_values`` and the corresponding ``branch_expr``. A branch may combine multiple ``case_value`` expressions via a comma: .. code-block:: csl switch (input) { case_value1, case_value2 => branch_expr1n2, case_value3 => branch_expr3, ... } A switch statement will attempt to match ``input`` with one of the provided ``case_value`` expressions. If a match is found the corresponding branch will be selected and the respective ``branch_expr`` will be executed. If no match is possible, the ``else`` branch will be selected as the default and the corresponding ``else_expr`` will be executed. ``case_value`` expressions must be comptime-known and coercible to the type of the ``input`` expression. They must also be unique. All ``branch_expr`` expressions (including the ``else_expr`` expression, if present) must have the same type. If ``input`` is known at compile-time, the ``branch_exprs`` corresponding to the branches not-taken are not semantically checked by the compiler, but they must still be syntactically valid. A switch can also be used as an expression. In this scenario all ``branch_expr`` expressions (including the ``else_expr`` expression, if present) must be able to be coerced to the common requested type: .. code-block:: csl fn foo(e: my_enum) i16 { // All branch_exprs and the else_expr are coerced to 'i16' which is the // type requested by the 'return' expression. return switch (e) { my_enum.A => 1, my_enum.B => -10, my_enum.C => 42, else => 100 }; } Branches do not fall through. If fall through behavior is desired, ``case_value`` expressions can be combined and if-statements can be used as follows: .. code-block:: csl switch (input) { 0, 1 => { if (input == 0) { // Logic for case 0 } // Common logic for cases 0 and 1 }, ... } A switch statement must cover all possible values for a given ``input`` expression type either explicitly by specifying a ``case_value`` for each possibility or implicitly through the ``else`` branch: .. code-block:: csl var int_input: i16 = ...; switch (int_input) { -5, 0 => ... // ERROR: Not all possible 'i16' values are covered. An 'else' branch is // needed. } const my_enum = enum { A, B, C }; var e: my_enum = ...; switch (e) { my_enum.A, my_enum.B => ..., my_enum.C => ... // OK! No 'else' branch is needed since all possible 'my_enum' values are // covered. } Operations on integer, floats and booleans ------------------------------------------- The following expressions are supported on integer or floating-point values: - ``a + b`` (addition) - ``a - b`` (subtraction) - ``a * b`` (multiplication) - ``a / b`` (division) - ``a += b`` (addition with assignment) - ``a -= b`` (subtraction with assignment) - ``a *= b`` (multiplication with assignment) - ``a /= b`` (division with assignment) - ``-a`` (negation) The following expressions are supported on integer values: - ``a % b`` (remainder from integer division) - ``a << b`` (shift left) - ``a >> b`` (arithmetic shift right if ``a`` is signed, otherwise logical shift right) - ``a & b`` (bitwise AND) - ``a | b`` (bitwise OR) - ``a ^ b`` (bitwise XOR) - ``a %= b`` (remainder from integer division with assignment) - ``a <<= b`` (shift left with assignment) - ``a >>= b`` (shift right with assignment) - ``a &= b`` (bitwise AND with assignment) - ``a |= b`` (bitwise OR with assignment) - ``a ^= b`` (bitwise XOR with assignment) - ``~a`` (bitwise NOT) The following expressions are supported on boolean values: - ``a or b`` (logical OR) - ``a and b`` (logical AND) - ``!a`` (logical NOT) For binary operations, both operands must have exactly the same type, unless one of them is a ``comptime_int`` (see :ref:`language-comptime`). Shift operations are an exception to this rule, where the only constraint is that the right hand side operand must be an unsigned integer. The ternary operator -------------------- A ternary operator has similar syntax to an if-statement: .. code-block:: csl const x : i32 = if (cond) 0 else 1; Ternary operators do not require ``{}`` blocks, and may be used anywhere an expression is expected. Both the "then" expression and the "else" expression must have compatible types. If ``cond`` is known at compile-time, the branch not taken is not semantically checked by the compiler, but it must still be syntactically valid. In this case, the two expressions don't need to have compatible types. Comments -------- ``//`` begins a single-line comment. There are no multi-line comments in CSL. .. code-block:: csl // This function returns the value arg + 2 fn foo(arg : i16) i16 { var x : i16 = arg; // This and the next line are commented out: x will not be incremented by 1 // x += 1; x += 2; // Increment x by 2 return x; }