Getting Started#

The ISON language is a functional programming language that lives within the JSON or JSON5 syntax specification. That is, every ISON script file can be read as a standard JSON file. The ISON syntax only appears in strings within the JSON file.

In the following, the basic language features of ISON are presented. You can copy paste the code and run it in an Jupyter Notebook or directly in a Python file.

Note

If you have installed the module functional-json, you can run the ISON parser also from the command line with ison. For command line help, run ison -h.

Special ISON Statements#

All dictionary keys in a JSON file that start with double underscore “__” are treated as special language features. If they are not ISON language elements, they are either removed or are reported as error.

Key

Value Type

Description

__includes__

list of strings

List of relative/absolute file paths of files to include. Go there…

__lambda__

dictionary

Indicates that the parent dictionary is a lambda function. See Go there…

__global__

dictionary

Global variabel definition

__locals__

dictionary

Local variable definition

__func_globals__

dictionary

Global function variable definition

__func_locals__

dictionary

Local function variable definition

__runtime_vars__

dictionary

Runtime variable definition

__platform__

dictionary

Modify dictionary depending on current hardware platform. Go there…

__data__

dictionary

Only allowed inside __platform__ dictionary or one of its’ child dictionaries.

Values#

As a first example, we will simply replace a value with another one.

import ison

# First we define a dictionary of macro values that can be replaced.
# When using the CML for Catharsys configuration files, this part is
# done by Catharsys.
dicVars = {"hello": "world"}

# Now we define the dictionary that is processed.
# This is your configuration json structure.
dicData = {"value": "$hello"}

dicResult = ison.run.Run(xData=dicData, dicConstVars=dicVars)

print(ison.run.ToString(dicResult))
{
    "value": "world"
}

Variables can be accessed in two ways:

  • Using the full syntax: ${variable}

  • Using the variable syntax: $variable

The full syntax will later be used to apply functions, as in $sum{1, 2}.

If a variable is not found, this is not an error, but the element is just left as it is. This is done to enable parsing in mutliple stages, as more data becomes available.

dicData = {"value": "$hello"}

dicResult = ison.run.Run(xData=dicData)
print(ison.run.ToString(dicResult))
{
    "value": "$hello"
}

Variables#

You can define variables within a dictionary, which can be accessed by other parts of the dictionary. These variables can either be declared as local or global variables.

dicData = {
    "__globals__": {
        "hello": "world",
    },
    "mValues": {
        "__locals__": {
            "hello": "today",
        },
        "value": "$hello",
    },
    "value": "$hello",
}

# Set 'bStripVars' to False, to see the variables dictionaries in the output.
dicResult = ison.run.Run(xData=dicData, bStripVars=True)
print(ison.run.ToString(dicResult))
{
    "mValues": {
        "value": "today"
    },
    "value": "world"
}

Local and global variables are evaluated first, before the remainder of the dictionary is processed. If you want to define variables that are executed every time they are referenced, you need to place the variables in __func_globals__ or __func_locals__. For example,

dicData = {
    "__func_globals__": {
        # This variables will calculate a uniformly distributed random variable
        "fRand": "$rand.uniform{0, 1}",
    },
    "__globals__": {
        "fA": "$fRand",
        "fA2": "$fA",
        "fB": "$fRand",
    },
    "fA2": "$fA2",
    "fA": "$fA",
    "fB": "$fB",
    "fC": "$fRand",
}

dicResult = ison.run.Run(xData=dicData, bStripVars=False)
print(ison.run.ToString(dicResult))
{
    "__func_globals__": {
        "fRand": "$rand.uniform{0, 1}"
    },
    "__globals__": {
        "fA": 0.9907603148150335,
        "fA2": 0.9907603148150335,
        "fB": 0.7556173456393716
    },
    "fA2": 0.9907603148150335,
    "fA": 0.9907603148150335,
    "fB": 0.7556173456393716,
    "fC": 0.038020755108450044
}

Here, the variables fA and fB are evaluated first, before the elements outside the variable dictionaries. fC executes the function referenced by fRand again, since fRand is not pre-evaluated.

Lists and Dictionaries#

Lists and dictionaries are defined in the standard JSON way, with [] and {}, respectively. Elements of lists and dictionaries are both accessed using the colon operator :. Here is an exampe,

import ison

dicData = {
    "__globals__": {
        # Define a list of values
        "lA": [1, 2, 3],
        # Define a dictionary of values
        "mB": {
            "a": 1,
            "b": 2,
        },
    },
    
    # Pick the second value from the list
    "fA": "${lA:1}",
    # Pick element 'b' from the dictionary
    "fB": "${mB:b}",
}

dicResult = ison.run.Run(xData=dicData, bStripVars=False)
print(ison.run.ToString(dicResult))
{
    "__globals__": {
        "lA": [
            1,
            2,
            3
        ],
        "mB": {
            "a": 1,
            "b": 2
        }
    },
    "fA": 2,
    "fB": 2,
    "__func_globals__": {}
}

You can also use the colon operator for nested objects, as shown in the next example.

import ison

dicData = {
    "__globals__": {
        # Define a dictionary of structured values
        "mB": {
            "lU": [1, 2, 3],
            "mX": {
                "lA": [1, 2, 3],
                "lB": [4, 5, 6],
            },
        },
    },
    
    # Pick a nested value
    "fA": "${mB:mX:lA:1}",
}

dicResult = ison.run.Run(xData=dicData, bStripVars=False)
print(ison.run.ToString(dicResult))
{
    "__globals__": {
        "mB": {
            "lU": [
                1,
                2,
                3
            ],
            "mX": {
                "lA": [
                    1,
                    2,
                    3
                ],
                "lB": [
                    4,
                    5,
                    6
                ]
            }
        }
    },
    "fA": 2,
    "__func_globals__": {}
}

Functions#

ISON defines a number of functions that you can call with the syntax

    "$func{arg1, arg2, [...]}"

where func is the function name and arg1, arg2, etc. are the function arguments. Just as with variables, the function expression will be replaced by the result of the function. If the function call is the only element in the JSON string, then the whole string is replaced by the function result. For example, if the function results in a list, then the string with the function call is replaced by the list.

Here is an example:

import ison

dicData = {
    "result": "$range{3}",
}
 
dicResult = ison.run.Run(xData=dicData, bStripVars=True)
print(ison.run.ToString(dicResult))
{
    "result": [
        0,
        1,
        2
    ],
    "__globals__": {},
    "__func_globals__": {}
}

Functions can also be nested, as in this example,

import ison

dicData = {
    "result": "$sort{$range{3}, true}",
}
 
dicResult = ison.run.Run(xData=dicData, bStripVars=True)
print(ison.run.ToString(dicResult))
{
    "result": [
        2,
        1,
        0
    ]
}

Unrolling Arguments#

Lists can also be “unrolled” as function arguments, so that each list element becomes one function argument. This can be done by prepending the argument that is to be unrolled with a “*”, as in this example.

import ison

dicData = {
    # This prints a list of numbers
    "result 1": "$print{$range{3}}",
    # This prints three lines each with one element of the list
    "result 2": "$print{*$range{3}}",
    # This calculates the sum of all list elements
    "sum": "$print{Sum of $range{3}: $sum{*$range{3}}}",
}
 
dicResult = ison.run.Run(xData=dicData, bStripVars=True)
# print(ison.run.ToString(dicResult))
[0, 1, 2]
0
1
2
Sum of [0, 1, 2]: 3

Literal arguments#

Sometimes it is helpful to pass an argument to a function without processing the argument before the function sees it. This is done by using a “^” as first element of an argument.

import ison

dicData = {
    # Make the whole argument to print a literal argument which is not parsed
    # before it is passed to the function.
    "literal": "$print{^Sum of $range{3}: $sum{*$range{3}}}",
    # Split it into two arguments (and use the default separator ", "). The
    # first argument is a literal string, the second is a parsed value.
    "sum": "$print{^Sum of $range{3}:, $sum{*$range{3}}}",
}
 
dicResult = ison.run.Run(xData=dicData, bStripVars=True)
# print(ison.run.ToString(dicResult))
Sum of $range{3}: $sum{*$range{3}}
Sum of $range{3}:
3

Tuples#

Some functions, like the !foreach function, can process tuple arguments (see documentation). These can be defined using “()”. Note that this is only possible as an argument of a function. Tuples cannot be defined as separate variables. The function $group{} returns a list of tuples, that can be unrolled and used in the !foreach function, to call a lambda function repeatedly with a set of values. Tuples are converted to lists when they are the final result of a function and need to be embedded in JSON.

import ison

dicData = {
    "result": "$print{$group{$range{3}, $range{3,5}}}"
}
 
dicResult = ison.run.Run(xData=dicData, bStripVars=True)
# print(ison.run.ToString(dicResult))
[(0, 3), (1, 4), (2, 5)]

Strings#

All ISON commands are contained in a string within a JSON file. Therefore, all arguments you pass to an ISON function are strings, which may be transformed to a numerical value, if the function expects a number. However, sometimes the formatting of a string conflicts with the syntax of an ISON function. For example, if you want to print a text with the $print{} function that contains a comma, then ISON would interpret this as two parameters passed to the function. In this case, you need to enclose the string you want to print either in single quotes ' or single backward quotes.

The single quotes ensure that everything between them is regarded as a single string. However, the quotes themselves are also part of that string. With backquotes, however, the quotes are stripped from the string before it is passed into the function.

Here is an example.

import ison

dicData = {
    # The single parameter of the function is just a string
    "_1": "$print{1. Hello World}",
    # The comma here, is interpreted as a separator between parameters.
    # If multiple parameters are given, each is printed on a new line.
    "_2": "$print{2. Hello, World!}",
    # Enclosing the string in single quotes, will prevent the comma from
    # being interpreted as a separator. However, the single quotes will
    # also be printed.
    "_3": "$print{'3. Hello, World!'}",
    # Enclosing the string in backticks, will prevent the comma from
    # being interpreted as a separator. The backticks will not be printed.
    # In fact, after interpreting everything between the backticks as a single
    # parameter, the backticks are stripped from the parameter.
    # The function then only receives the string 'Hello, World!' as a parameter, without quotes.
    "_4": "$print{`4. Hello, World!`}",
}

dicResult = ison.run.Run(xData=dicData, bStripVars=False)
# print(ison.run.ToString(dicResult))
1. Hello World
2. Hello
World!
'3. Hello, World!'
4. Hello, World!