Skip to content

Compiler Built-ins

Beyond the commands Kontakt provides, CKSP ships a small set of built-in functions of its own. They are written like ordinary function calls, but the compiler resolves every one of them while translating: none of them appears as a call in the generated script. num_elements(my_array) becomes a constant, pairs(my_array) becomes a loop counter, and Zone.storage(.id) becomes the array the compiler laid out for that member.

That also explains their main restriction. A compiler built-in is not a value: it cannot be stored in a variable that holds a function, passed to a higher-order function, or redefined. It only exists in the positions described below.

  • Working With Arrays


    num_elements() reports the size of an array or one of its dimensions, search() and sort() extend their KSP counterparts with wildcard notation.

    Array Built-ins

  • Driving For-Each Loops


    pairs() yields index and value together, range() generates a sequence of numbers without an array to back it.

    Iteration Built-ins

  • Working With Objects


    search_by() finds an object by one of its fields, storage() hands out the array a member is stored in, use_count() reports how many references an object still has.

    Object Built-ins

  • Conversion and Output


    bool() turns a number into a boolean, and message() accepts more than the single argument KSP allows.

    Conversion and Output


Overview

Built-in Signature Returns Origin
num_elements num_elements(array)
num_elements(array, dimension)
int extends KSP
search search(array, value)
search(array, value, from, to)
int extends KSP
sort sort(array, direction)
sort(array, direction, from, to)
void extends KSP
pairs pairs(array) index/value pairs CKSP only
range range(stop)
range(start, stop)
range(start, stop, step)
number sequence CKSP only
search_by search_by(array, .field, value) object or nil CKSP only
storage Struct.storage(.member) array of the member type CKSP only
use_count use_count(pointer) int CKSP only
bool bool(value) bool CKSP only
message message(value, ...) void extends KSP

num_elements

Returns the number of elements of an array. The array is passed without brackets. For a multidimensional array a second argument selects the dimension; without it, or with 0, the function reports the total number of elements of the flattened array.

1
2
3
4
5
6
7
declare my_array[10]: int[]
declare my_ndarray[10, 20]: int[][]

num_elements(my_array)          // 10
num_elements(my_ndarray, 1)     // 10 - first dimension
num_elements(my_ndarray)        // 200 - flattened
num_elements(my_ndarray[1, *])  // 20 - via wildcard notation

Since array sizes are known at compile time, the call is usually folded into a literal and costs nothing at runtime.

Getting the Size of an Array · Size of a Specific Dimension


search and sort

Both commands exist in KSP. CKSP extends them so they also accept wildcard notation, which lets them address a single dimension of a multidimensional array.

search(my_ndarray[2, *], 5)  // searches the third row for the value 5
sort(my_ndarray[3, *], 0)    // sorts the fourth row in ascending order

Searching and Sorting Multidimensional Arrays

The first argument must be a reference

search and sort operate on an array in place, so their first argument has to name an array. An expression that merely evaluates to an array — a call to a function returning int[], for instance — is rejected.


pairs

0.0.7

Only usable as the range of a for-each loop. It yields the index alongside the value, so the loop can declare two iterators instead of one.

1
2
3
4
declare array[5] := ["3", "4", "6", "8", "10"]
for key, val in pairs(array)
    message(key, val)
end for

pairs works with everything a for-each loop accepts: arrays, multidimensional arrays, wildcard slices, initializer lists and range().

Iterating Over Arrays


range

0.0.7

Generates a sequence of numbers for a for-each loop without an array to back it. stop is exclusive, start defaults to 0 and step to 1.

1
2
3
for num in range(10)
    message(num)  // 0, 1, 2, ... 9
end for
1
2
3
for num in range(10, 0, -1)
    message(num)  // 10, 9, 8, ... 1
end for

Negative Step Value

To count down, step has to be negative. A start greater than stop alone produces an empty range and the loop body never runs.

Iterating Over a Range of Numbers


search_by

0.1.0 experimental

Finds an object in an array of objects by the value of one of its fields, and returns that object — or nil if no element matches. The field is named by a member path: a leading dot followed by the member name, written without a receiver, since the array supplies the object type.

struct Zone
    declare id: int
    declare velocity: int
end struct

on init
    declare z1 := Zone(7, 100)
    declare z2 := Zone(9, 110)
    declare zones[2] := [z1, z2]

    declare hit := search_by(zones, .id, 9)
    message(hit.velocity)  // 110

    if (search_by(zones, .id, 42) = nil)
        message("no zone with id 42")
    end if
end on

The path may reach through several objects. search_by(items, .child.id, 8) finds the Item whose child carries the id 8, and the compiler searches from the leaf back to the queried array.

The lookup is not a linear scan over the array. Every member of a struct is stored in one array holding that member's value for every instance, so CKSP can use KSP's own search on that storage and only then check whether the instance it found is part of the queried array. Duplicate field values are handled by continuing the search past each candidate.

Requirements

The first argument has to be a one-dimensional array whose elements are objects, and every segment of the path has to name a member that holds a single value per instance. An array member or a static member has no per-instance storage to search and is rejected. The searched value and the member must have matching types.


storage

0.1.0 experimental

CKSP gives every struct member one array that holds the value of that member for all instances, laid out back to back. pitch of every Note therefore lives in a single array. That array has no name in the source language, and storage is the way to reach it.

It is called on the struct itself, like a static method, and takes a member path naming the member whose storage is wanted:

struct Zone
    declare id: int
    declare velocity: int
end struct

function report(ids: int[])
    message(num_elements(ids), ids[0], ids[1])
end function

on init
    declare z1 := Zone(7, 100)
    declare z2 := Zone(9, 110)

    report(Zone.storage(.id))  // 2, 7, 9
end on

The result is a normal array of the member's type, handed over by reference rather than copied — writing to it writes through to the instances. Its length is the number of instances the compiler reserved for the struct, which is derived from how many times the struct is constructed, not from how many instances are alive at a given moment.

Which members have storage

Only a member that holds a single value per instance is stored this way. Array and multidimensional-array members are spread over more dimensions, and static members share one value across all instances, so neither has a storage array and both are rejected. The path has to name a single member: for a member of a nested struct, ask that struct instead, as in Child.storage(.id).

storage is a reserved method name

Since Struct.storage(.member) is resolved against the member the path names, a method called storage in a struct would never be reachable. Declaring one is therefore an error.

Where the result can be used

The array only materialises at the very end of compilation, so today the result can be handed to a function parameter but not yet used as a for-each range, indexed directly, or passed to num_elements(), search() or sort(). This applies to every function returning an array and is tracked in issue #121.


use_count

Returns how many references an object currently has. CKSP manages struct memory by reference counting, so this is the value that decides whether an instance is released when a pointer goes out of scope or is deleted.

1
2
3
4
5
6
7
declare note_ptr: Note := new Note(60, 100)
declare alias: Note := note_ptr

message(use_count(note_ptr))  // 2

delete alias
message(use_count(note_ptr))  // 1

Useful when tracking down a leak from a circular reference, where the count never drops to zero on its own.

Memory Management


bool

Converts a value to a boolean: every non-zero number becomes true, zero becomes false.

message(bool(3), bool(0))  // 1, 0

Real numbers are converted to an integer first, so a value between 0 and 1 becomes false.


message with several arguments

KSP allows message exactly one argument. CKSP accepts any number and joins them into a single string separated by ", ", which saves writing out the concatenation.

message("state", $EVENT_ID, my_array[0])
message("state" & ", " & $EVENT_ID & ", " & %my_array[0])

Since the result is one string, the usual string conversion rules apply to every argument.

Literals and Expressions