Kronos Language Syntax

back to top

A Very Quick Reference

Overview

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.

Expressions

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

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

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.

Literals

String literals can be entered as quoted strings;

"Hello World"

Symbols

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.

x
MyVariabeName
some-symbol123
attention!

Tuples

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

Functions

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.

Factorial(#5)
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' */

Definitions

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

Symbols

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.

Tie-in

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.

Ignore-binding

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).

Functions

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.

Polymorphism

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

SumNumbers(numbers)
{
  (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)
{
  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.