Language

Characteristics

The Brain4it programming language (BPL) is inspired in functional languages like LISP or Scheme, that have been widely used over the years in the development of artificial intelligence applications.

BPL is a simplified version of these languages that depite of being much smaller, offers similiar possibilities.

Its main features are:

Basic data types

BPL only supports 3 basic data types:

BPL also defines the literal null.

All these data types are immutable, which means that its value cannot be changed.

References

A reference is a name, symbol or path that references an object of any type.

There are two types of references:

When a soft reference includes slashes (/) it is a path reference. Path references indicate a path through a list sctructure to access the data. The components of a path reference can be strings to access by name or integers to access by index within the list, as discuss below.

The name of a non path reference may contain any character except space, slash (/), double quote (") and parenthesis and the first character can not be a digit.

Soft references can not match the name operator =>, a built-in function or the literals null, true and false, NaN, Infinity and -Infinity.

All references are immutable because its name can not be changed.

Lists

The list is the only data structure supported in the Brain4it programming language.

Lists allow you to represent both data and code. They are expressed by parenthesis and may contain from 0 to N elements of any type, separated by spaces. Examples:

Elements in a list can be labeled with a name. That name is a string that must be unique within the list. The arrow (or name) operator => is used to set a name for an element. Examples:

Elements in a list can be accessed by position or by name. For example, if we assign the variable persons this list:

(("name" => "John" "age" => 34 "0" => 45) ("name" => "Mary" "age" => 31))

Later we will see in more detail how to access the elements of a list.

A list is mutable because you can add and remove elements to it.

Evaluation

The Brain4it programming language has the following evaluation rules:

Examples:

Functions

The Brain4it programming language has 2 types of functions:

Built-in functions

BPL has built-in functions to perform basic operations like assigment of variables, conditionals and loops. Also offers a rich set of functions to work with lists, strings, numbers and dates. Next we will see some of the most common built-in functions:

The assigment of variables is done through the set function.

(set result (+ 4 5))

In this example, the variable (or reference) result takes the value 9.

The set function, like many other built-in functions, do not evaluate the reference that is going to assign a value (result in the example). Only when the first argument is not yet a reference the set function will evaluate it to get the final reference, like in this example: (set (get result) 8) where the value 8 will be assigned to the reference pointed by result.


A conditional instruction can be done with the if function. Its syntax is:

(if condition action_when_true action_when_false)

Example:

(if (> level 7.5) "OK" "KO")

If the value of level is greater than 7.5 the funtion will return "OK", otherwise will return "KO".

A sequence of actions can be grouped with the do function:

(if (> level 7.5)
  (do
    (send_email "OK")
    (switch_alarm 0)
  )
  (do
    (open_door)
    (send_email "KO")
    (switch_alarm 1)
  )
)

When you have to code an if-then-else cascade its more convenient to use the cond function:

(cond
  (when condition_1
    action
    ...
    action
  )
  ...
  (when condition_n
    action
    ...
    action
  )
)

The cond function only executes the actions of the first when clause which condition evaluates to true.

Example:

(cond
  (when (= state 1)
    (set speed 4)
  )
  (when (= state 2)
    (set speed -4)
  )
  (when true
    (set speed 0)
  )
)

In that example, if the initial value of state is 1 the variable speed will take the value 4.


For doing loops, BPL has the while function, that works like the while statement of many other programming languages:

(while condition
  action
  ...
  action
)

while the condition evaluates to true, the actions inside the while will be executed.

Example:

(while (>= a 0)
  (-- a 2)
)

In that example, variable a will be decremented by 2 until its value be less than 0.


An alternative to the while function is the for function, that has a syntax pretty similiar to the for statement of C like languages:

(for
  initialization_code
  condition
  iteration_code
  action
  ...
  action
)

that is equivalent to:

(do
  initialization_code
  (while condition
    action
    ...
    action
    iteration_code
  )
)

Example:

(for (set i 0) (< i 10) (++ i)
  (prepare_data i)
  (send_data i)
)

That code will execute prepare_data and send_data 10 times.

As you probably have guessed, the arguments of a built-in function are not evaluated "a priori". The function evaluate them only when it considers that it is necessary. Other programming languages like C, Java or Javascript work different because they evaluate all the arguments before calling a function.


The more common built-in functions to manage lists are list, get, put and remove.

The list function creates a new list and adds to it the evaluation of its arguments:

Example:

(list (+ 3 4) "a" => (- 5))

will return (7 "a" => -5)

The get function allows you to obtain an element of a list accessing by index or by name. Its general form is:

(get lst spec)

where lst is the list, and spec is the index or name.

Examples:

(get (1 2 "a" => 3) 1)

In that example the function will return the element at index 1, which is the value 2 (the index of the first element is 0).

(get (1 2 "a" => 3) "a")

here the get function will return the element whose name is "a", which is the value 3.


To put an element into a list, BPL has the put function. Its syntax is:

(put lst spec value)

where lst is the list, spec is the index or name and value the element to put.

Examples:

(do
  (set my_list (1 2 "a" => 3))
  (put my_list 0 "Mary")
  (put my_list "a" "Rick")
  (put my_list 5 99)
)

After executing that code, variable my_list will be:

("Mary" 2 "a" => "Rick" null null 99)

When the specified index is outside the size of the list, that list is automatically enlarged to put the element, filling with null the new positions in the list.

Removing elements from a list is done with the remove function, that has this syntax:

(remove lst spec)

where lst is the list, and spec is the index or name of the element to remove.


Built-in functions are organized in libraries. The Core library contains the basic functions that all programs need and is always present in the Brain4it servers.

Some other libraries may also be available to access external services or hardware resources. See the Library catalog for more information.

User defined functions

An anonymous user function is defined this way:

(function (parameter ... parameter)
  action
  ...
  action
)

where the parameter elements represent the parameters that takes the function, and the action elements the actions to execute.

The value returned by the function is the result of evaluating the last action.

Example:

(function (x y) (* x (sin y)))

That function takes two parameters x and y and will return the result of evaluating x * sin(y).

We can invoke an anonymous function using the call function:

(call (function (x y) (* x (sin y))) 45.2 3.6)

To give that function a name, assign the function to a variable:

(set sin_product (function (x y) (* x (sin y))))

and then invoke the function with a calling list like this:

(sin_product 45.2 3.6)

The parameters of a user defined function can be defined in three ways:

When a parameter is not specified in the calling list, its value will be null.

For example, if we want to define a function that takes a mandatory parameter value and 2 optional parameters offset and factor, we can define it like this:

(set fn
  (function (value "offset" "factor" => f)
    (* f (+ value offset))
  )
)

If we invoke that function with this calling list:

(fn 7)

offset and f will be null and the function will return the value 7, but in this case:

(fn 7 "offset" => 5)

only f will be null and the result will be 12. In this other call:

(fn 7 "factor" => 2)

offset will be null and the returned value will be 14 and here:

(fn 7 "offset" => 5 "factor" => 2)

we will obtain the value 24.

User defined functions unlike built-in functions receive its arguments already evaluated. If you need to pass an expression to a user defined function without evaluating it, use the quote function. Example: (process_data (quote (+ 1 2))). The process_data function may evaluate that argument with the eval function.

BPL supports recursion, that is functions that call themselves.

For example, the factorial function can be defined in this way:

(set fact
  (function (x)
    (if (< x 1) 1 (* x (fact (- x 1))))
  )
)

Exceptions

BPL supports exceptions as the Java programming language.

Exceptions are thrown in these cases:

Exceptions can be handled with the try function, that works like the Java try-catch-finally statement. Its general syntax is:

(try
  main_action
  (ex_var catch_action ... catch_action)
  finally_action
)

Let's see an example:

(set res (get_resource))
(try
  (do
    (process_resource res)
    (notify_result res)
  )
  (ex
    "ConnectException" => (abort_process)
    "SQLException" => (abort_process)
    "IOException" => (process_resource res)
    "*" => (notify_error ex)
  )
  (release_resource res)
)

When an exception occurs the ex_var variable (ex in the example) will contain a list like this:

(
  "ConnectException"
  "message" =>
  "Can't connect to http://brain4it.org"
  "code" => (http "GET" url)
  "stack" => (do_work process_resource)
)

where the first element is the exception type (usually the simple name of the Java Exception class) and the next elements (not always present) are the detail of the exception:

The executed catch_action is that whose name matches the exception type ((abort_process) in the example).

If there is no catch_action that matches the exception type, then the catch_action whose name is "*" will be executed.

If the main_action throws an exception that is not handled by any catch_action that exception will be propagated to the superior function.

The finally_action will always be executed whatever an exception is thrown or not.

The value returned by the try function is the result of evaluating the main_action or in case that main_action throws an exception, the result of evaluating the catch_action that handles that exception.

The try function differs from the Java try-catch-finally statement in these aspects:

The throw function allow us to throw an exception. Example:

(throw "UserError" "The value entered must be positive")

where the first argument is the exception type and the second argument (optional) is the message of the exception.

The throw function can also accept an exception list as the first argument:

(throw ("UserError" "message" => "The value entered must be positive"))

Multi-threading

The Brain4it programming language supports running code in multiple execution threads.

Every time a command is launched, the server creates an executor thread that will run that command until it ends.

We can list all the executors currently running within a module with the executors function:

(executors)

the result could be a list like this:

("45" => (while true (eval rules) (sleep 1000)) "47" => (executors))

This list contains for each executor the code that it is running labeled with the executor_id, the executor identifier.

If we want to interrupt the executor identified by "45", simple type:

(kill 45)

This command invokes the Thread.interrupt() Java method on the executor thread to force its finalization.

Sometimes we want to run a code without waiting for the result. That can be achived with the spawn function:

(spawn (do (sleep 5000) (beep))

spawn creates the new executor thread and immedialety returns its executor_id.

To synchronize multiple executor threads, BPL offers functions similiar to those that exists in the Java programming language: sync, wait and notify. See the core library documentation for details.

Scopes

All executor threads running inside a Brain4it server see two variable scopes:

Every time we call a function, a new local scope is created. The arguments of the function are put into that scope. When the function returns, the scope is destroyed.

If a variable is set with a statement like this:

(set alfa 9)

that variable will be created in the global scope (also inside a function) and thus will be visible by other executors in the same module.

If we want to create a variable in local scope we must declare the variable this way:

(local alfa)

That variable will last as much as the local scope where it was created.

Variable scopes in BPL are implemented as lists. We can obtain the global scope list with the global-scope function:

(global-scope)

that may return a list like this:

("alfa" => 9 "square" => (function (x) (* x x)))

The local scope is obtained with the local-scope function.

Both scopes, global and local, can be manipulated as normal lists.

To remove a variable from the scope where it is defined, you can use the delete function:

(delete alfa)

To check if a variable is defined in the current scopes (local or global), use the exists function:

(exists alfa)
Top