Declarations
Declarations and Variables
Before variables can be used in the code, they must be declared. Variable declarations can be written using the following syntax:
- The
declarekeyword is used to declare variables. - The name of the variable is written after the
declarekeyword. The variable name must start with a letter and can contain letters, numbers, underscores and dots. Variable names are case-sensitive. - The type of the variable is written after a colon. The type can be
int,real,string,bool, or an object type.
Type Annotations
The type of the variable is written after a colon. However, this type annotation is optional, and the compiler will attempt to infer the type if it's not provided.
Variables can be declared either individually or together. However, when declaring variables, the declare keyword must be placed at the beginning of each line.
Type-Neutral Initialization
When variables are declared without being assigned a value, they are always initialized with their type-neutral values. This means that integers are initialized to 0, strings to "", real variables to 0.0, boolean variables to false and object pointers to nil.
Variables can also have an optional inline assignment. In this case, the value is specified after the assignment operator (:=). Just as multiple variables can be declared in one line, multiple variables can also be initialized in one line. In the example below, the highlighted line initializes var2 to 3.14 and var3 to "pi".
If more variables are referenced on the left side of a declaration than there are assignment values on the right side, the compiler will attempt to initialize the remaining variables or arrays with the last provided assignment value.
Constant Variables
To declare constants, i.e., variables that cannot be changed after their declaration, the const keyword can be used. Constants must be declared inline with an assignment, meaning both the declare keyword and an assignment are required.
Constant Naming Convention
Constants are typically written in uppercase letters with underscores separating words. This is a common naming convention in programming languages.
This requirement holds everywhere, including for const members of a struct. A constant that never receives a value would hold whatever its storage happened to contain, so the compiler rejects it at its declaration.
Constant Blocks
When several related constants belong together, they can be grouped into a Constant Block, similar to the concept of enums in other languages. The block is written between const <name> and end const, and may be declared at the top level or inside any callback.
Each entry becomes a fully-fledged integer constant, accessed through the block name using dot notation. Because the block also keeps its entries in a backing array, they can additionally be read by their zero-based index, and the number of entries is available through num_elements.
Automatic Numbering
Assigning a value to an entry is optional. An entry without an explicit value continues counting from the previous one, with the very first entry defaulting to 0. Assigning an explicit value resets this running counter, so every following implicit entry counts up from there.
An entry's value may be any constant integer expression, including a reference to an entry of an already declared block through its fully qualified name.
Comma-Separated Entries
Multiple entries can be declared on a single line, separated by commas. They follow the same rules as one-line variable declarations: entries without a value are numbered automatically, and if fewer values are supplied than there are entries, the last value fills the remaining ones.
Constant Block Constraints
Constant Blocks hold integer constants only; string or real entries are rejected by the compiler. A type annotation, if given, belongs on the individual entry (e.g. WHITE: int := 0x9FFFFFF), not on the const <name> header. Because each entry is compiled to a distinct constant, entries cannot reference one another by their bare name — always use the fully qualified <name>.<entry> form.
Constant Blocks in Structs
A block can also be declared inside a struct, which is the natural place for the states or modes that belong to one data structure. Inside a struct body the block is written static const:
| A Struct-Scoped Constant Block | |
|---|---|
The entries are addressed through the struct name, as Voice.State.ACTIVE. Since they remain compile-time constants, they fold into their use sites and emit no storage, which means they can also serve as an array dimension:
The static keyword is required here rather than optional. A block holds compile-time values and therefore cannot vary between instances, so it is always shared by the whole struct — writing it as static const says the same thing the modifier says on a static member. A block declared with a bare const is reported as an error.
Declarations and Arrays
Arrays allow grouping individual values into a data structure. Arrays can be declared for any of the three basic types. For brevity, here are some one-line declarations. As with variables, type annotations are optional:
The size of the arrays must be defined at declaration and specified by an integer literal or a constant. However, if the array is initialized in the same line as it is declared, the size can be omitted.
Since the array is initialized with an initializer list, the compiler automatically sets the array size to 5 and allocates an array with the values [1,2,3,4,5]. The size constant of any array can always be retrieved later using the num_elements function.
If all indices of an array need to be set to the same value, the array can be declared as follows:
If there are fewer elements in the initializer list than the size of the array, the last value of the initializer list will be used to initialize the remaining indices of the array.
Initializer Lists in KSP
KSP also offers the option to use initializer lists for arrays. However, those can only be used for numbers, have to be given in the same line as the declaration and use parentheses instead of square brackets. To offer better compatibility with old codebases, cksp allows KSP initializer lists with parentheses when declaring arrays.
Declarations and Multidimensional Arrays
To represent matrices or larger datasets more easily, multidimensional arrays can be used. The size of each dimension is specified by comma-separated constants or integer literals within the brackets during the declaration. In the optional type annotation, the number of opening and closing brackets represents the number of dimensions. As with one-dimensional arrays, multidimensional arrays can be declared for any of the basic types.
The extra parentheses around the groups of three values in each dimension are optional. Otherwise, multidimensional arrays behave similarly to one-dimensional arrays when declared.
In the above example, message will output the value 1. As with one-dimensional arrays, the sizes of the individual dimensions can be accessed by the num_elements function as explained and an extra parameter can be passed to specify the dimension.
Persistent Declarations
To make variables and arrays persistent, vanilla KSP provides built-in functions such as make_instr_persistent(), make_persistent(), and read_persistent_var(). As a shorthand for these functions, the keywords read, pers, or instpers can be used directly within a declaration. Here, read is a shortcut for calling the latter two functions.
When multiple declarations are made in one line, all additional declaration keywords, except for declare, must precede each variable.
Of course, array and variable declarations can be mixed together in one-line syntax without being separated, as shown in the following declaration chain: