Kronos Language Syntax

back to top

A Very Quick Reference


Kronos programs are built from expressions and definitions.

An expression describes a sequence of mathematical or logical operations to produce a result from one or more initial values.

A definition binds an expression to a symbol. Subsequently, the symbol can be used to refer to the expression.


x + 3 * Crt:cos(y)

This expression describes a computation of a numerical result from the initial values of x, 3 and y.

Expressions are essentially a sequence of tokens. In the example above, the tokens are x, +, 3, * and Crt:cos(y).

Tokens are delimited by either white space or parentheses.


Constants are plain numbers. A plain number is interpreted as a 32-bit floating point real number. Prefices and suffices can be used to enforce a different number type;

3i      /* constant 3 as a 32-bit integer */
3.1415d /* constant 3.1415 as a 64-bit real */
9q      /* constant 9 as a 64-bit integer */
#6.54   /* special type of number Invariant */

Invariant numbers are prefixed with '#'.

Such numbers are important for two reasons; they never change during program execution and can thus be heavily optimized. The compiler can also use them for metaprogramming purposes.


String literals can be entered as quoted strings;

"Hello World"


Symbols can be names for variables or functions. They can include alphanumeric characters as well as some punctuation characters.

Each symbol must start with an alphabetical character.



A tuple is a grouping of data. Such grouping is expressed with parentheses.

(1 2 3 4 5)
A tuple of five numbers.
[1 2 "text" 3]
A list containing numbers, a string and a number.
[(1 2) (10 20) (100 200)]
A list of three vectors


A symbol and a tuple without an intervening whitespace is considered a function.

The symbol names the function, and the tuple specifies the arguments passed to the function.

Add(5 4)
Algorithm:Reduce(Add 1 2 3 4)
Some function calls
(somesymbol(1 3)) /* this is a function call in parentheses */
(somesymbol (1 3)) /* this is a 3-member tuple! */ 
Whitespace is important to disambiguate functions and tuples!

Infix Functions

The Kronos parser translates some infix operators to functions for a more readable syntax. The following relations apply:

a + b   /* Add(a b) */
a - b   /* Sub(a b) */
a * b   /* Mul(a b) */
a / b   /* Div(a b) */
a > b   /* Greater(a b) */
a < b   /* Less(a b) */
a == b  /* Equal(a b) */
a != b  /* Not-Equal(a b) */
a >= b  /* Greater-Equa(a b) */
a >= b  /* Less-Equal(a b) */
a & b   /* And(a b) */
a | b   /* Or(a b) */
a ^ b   /* Xor(a b) */

Because some infix characters are legal token characters, infix operators must always be surrounded by whitespace:

much - fun   /* becomes Sub(much fun) */
much-fun     /* refers to symbol 'much-fun' */


Actual programming in Kronos happens via definitions. For example, to hear an audio output, you define the audio-generating function.


Defining symbols is called binding.

Definitions are done via the equality sign.

a = 5 /* a is now 5 */
x = y / Sqrt(z) /* an expression tied to x */

Defined symbols are unlike variables in two accords; they actually contain the expression itself, rather than the value it results in. Secondly, typical to functional languages, once defined, they can never change.


Multiple symbols can be defined at once by defining a .

(a b c) = (1 2 3) /* a is now 1, b is 2... */

Tie-in is mostly useful when you receive a tuple that is bound to a symbol. The tuple can be deconstructed with a partial tie-in:

/* suppose list = (1 2 3 4 5) */
(x xs) = list /* x is now 1, xs is (2 3 4 5) */

Together with recursion, partial tie-in is used for loop-like constructs.


You can use '_' as a special symbol that never actually gets defined. Useful as a part of a tie-in, you can use it to ignore certain parts of the tuple.

someSurroundSignal = (l1 c1 r1 ls1 rs1 lfe)
(l2 _ r2 _) = someSurroundSignal

In this exampe, l2 and r2 were bound to l1 and r1, while the rest of the elements are just ignored.

The first '_' corresponds to c1 and the second one to the tuple (ls1 rs1 lfe).


Function definitions look like so;

MyFunction(x y z)
  MyFunction = x + y + z

The first line contains the function name and the argument tuple. In the consequent wavy brackets, the function bodies are given by way of defining a symbol eponymous to the function.

This particular example computes the sum of three numbers.

To call the function, attach an argument tuple;

MyFunction(1 2 3) /* returns 6 */

If you refer to the function name without arguments, it evaluates to the definition of the function itself. This way, you can use function definitions as data.


Multiple definitions for a function can be given at once. This allows the function to behave differently according to its arguments.

  (a b) = numbers
  SumNumbers = a + b
  /* second form */
  (x y z) = numbers
  SumNumbers = x + y + z

This function can sum either three or two arguments.

Polymorphism is often most useful in the case of recursion:

  SumNumbers = numbers
  /* second form */
  (n ns) = numbers
  SumNumbers = n + SumNumbers(ns)

This function can sum any number of arguments; while the 'numbers' argument can be tied-in to (n ns), the function calls itself with 'ns' as the argument and adds the result to 'n'. At some point, 'numbers' is going to be a single number, at which point the first definition will be used instead.