Table of Contents
This document is in state of creation and will further evolve. It provides a description of the classic C-based ToolBus as well as of the Java-based ToolBusNG. Eventually, it will exclusively focus on ToolBusNG. See the section called “To Do”.
Building large, heterogeneous, distributed software systems poses serious problems for the software engineer. Systems grow larger because the complexity of the tasks we want to automate increases. They become heterogeneous because large systems may be constructed by re-using existing software as components. It is more than likely that these components have been developed using different implementation languages and run on different hardware platforms. Systems become distributed because they have to operate in the context of local area networks.
Three aspects of heterogeneous, distributed, systems should be considered: coordination, representation and computation.
Coordination. Coordination is the way in which program and system parts interact with each other using, ordinary procedure calls, remote procedure calls (RPC), remote method invocation (RMI), and others.
Representation. Representation is the language and machine neutral format for data being exchanged between components.
Computation. Computation is done by specialized program code that carries out a specific task, e.g., providing a user-interface, providing database access, and the like.
Our key assumption is as follows:
A rigorous separation of coordination from computation is the key to flexible and reusable systems.
A system organization that respects this separation is shown in
We propose to get control over the possible interactions between software components (tools) by forbidding direct inter-tool communication. Instead, all interactions are controlled by a process-oriented script that formalizes all the desired interactions among tools. This leads to a component interconnection architecture resembling a hardware communication bus, and therefore we call it a ``ToolBus''.
Given the motivation for the ToolBus we can briefly summarize the requirements that the ToolBus should satisfy:
Provide a flexible interconnection architecture for software components that are not only written in different languages and executing on different hardware and software platforms, but are also running in a distributed fashion on a system of networked computers and devices. Rationale: it is more and more common that applications are built using existing commercial or open source components. This reduces implementation effort but increases the need for usable interconnection technology.
Provide good control over the communication between components. Rationale: component integration requires both control over the communication between components and over the data that are being exchanged (see next requirement).
Provide a uniform data exchange mechanism between heterogeneous components. Rationale: a common understanding about data formats is needed in order to exchange data between components.
The description of communication should be based on existing concurrency theory and provide the option for formal verification of the cooperation between software components. Rationale: when components are integrated that run on different machines or on multi-core machines, it is unavoidable that concurrency is taken into account and to use existing theory to describe it. The long term perspective of checking formal aspects of these cooperation is appealing for certain, safety-critical, applications.
Provide relatively simple application descriptions that can be understood by most programmers. Rationale: we don't want to frighten programmers by using formal notations.
Provide multi-lingual support, at least C, Java, ASF+SDF, Tcl/Tk, and possibly Perl, Python and Ruby should be supported. Rationale: various tools of interest are currently implemented in the first four languages, and the last three languages are interesting for future developments.
The global architecture of the ToolBus is shown in Figure 1.2, “Global organization of the ToolBus”. The ToolBus serves the purpose
of defining the cooperation of a variable number of
tools T
i
(i
= 1, ...,
m
) that are to be combined into a complete
system. The internal behaviour or implementation of each tool is
irrelevant: they may be implemented in different programming languages,
be generated from specifications, etc. Tools may, or may not, maintain
their own internal state. Here we concentrate on the external behaviour
of each tool. In general an adapter will be needed
for each tool to adapt it to the common data representation and message
protocols imposed by the ToolBus.
The ToolBus itself consists of a variable number of processes
P
i
(i
= 1, ...,
n
)[1]The parallel composition of the processes
P
i represents the
intended behaviour of the whole system. Tools are external,
computational activities, most likely corresponding with operating
system level tasks. They come into existence either by an execution
command issued by the ToolBus or their execution is initiated
externally, in which case an explicit connect command has to be
performed by the ToolBus. Although a one-to-one correspondence between
tools and processes seems simple and desirable, we do not enforce this
and permit tools that are being controlled by more than one process as
well as clusters of tools being controlled by a single process.
Communication inside the ToolBus.
Inside the ToolBus, there are two communication mechanisms
available. First, a process can send a message
(using snd-msg
) which should be received,
synchronously, by one other process (using
rec-msg
). Messages are intended to request a
service from another process. When the receiving process has completed
the desired service it may inform the sender, synchronously, by means
of another message (using snd-msg
). The original
sender can receive the reply using rec-msg
. By
convention, part of the original message is contained in the reply
(but this is not enforced).
Second, a process can send a note (using
snd-note
) which is broadcasted to other, interested,
processes. The sending process does not expect an answer while the
receiving processes read notes asynchronously (using
rec-note
). Notes are intended to notify others of
state changes in the sending process. Sending notes amounts to
asynchronous selective broadcasting. Processes will
only receive notes to which they have
subscribed.
Communication between ToolBus and tools.
The communication between ToolBus and tools is based on
handshaking communication between a ToolBus process and a tool. A
process may send messages in several formats to a tool
(snd-eval
, snd-do
, and
snd-ack-event
) while a tool may send the messages
snd-event
and snd-value
to a
ToolBus process. There is no direct communication possible between
tools. These communication patterns are shown in Figure 1.3, “Communication between ToolBus and tools”.
The execution and termination of the tools attached to the ToolBus can be explicitly controlled. It is also possible to connect or disconnect tools that have been executing independently of the ToolBus.
Knowledge separation. Equipped with the mechanisms provided by the ToolBus, careful control over application knowledge can be achieved as shown in Figure 1.4, “Knowledge separation in ToolBus-based application” where an application is depicted consisting of a user-interface (UI) and a database (DB). In a more conventional approach, elements of the user-interface, say a button, would be directly connected with functions in the database component and a strong coupling between the two components would be the result. Using the ToolBus, the two components can be completely oblivious of each other. It is only in the ToolBus script that they are configured to work together. The extra level of indirection introduced by the ToolBus thus leads to extra flexibility and decoupling.
After this brief motivation and explanation of the ToolBus architecture it is time to delve into more details. In the remainder of this chapter, we will have a look at the following topics:
ToolBus scripts (or Tscripts, for short).
How to write ToolBus tools.
A brief peek at the ToolBus implementation.
Historical notes
Tscripts describe how the tools in an application cooperate. They allow the definition of a collection of concurrent processes that can communicate with each other and with the tools in the application.
Tscripts make heavy use of terms, simple prefix expressions that are used to exchange structured data between processes and tools. Terms are recursively defined as follows:
A Boolean constant, integer constant, real constant, or string
constant is a term, e.g., true
,
37
, 314e-12
, or
"rose"
.
A value occurrence of a variable is a
term, e.g., X
, InitialAmount
,
or Highest-Bid
.
Variables always start with a capital letter. A value occurrence serves the purpose of using the current value of a variable.
A result occurrence of a variable is a
term, e.g., X?
, InitialAmount?
or Highest-Bid?
.
A result occurrence of a variable plays a role when this term is matched with another term. In the case that the match succeeds, the corresponding part of the other term is assigned to the result variable.
A single identifier is a term, e.g.,
f
, pair
, or
zero
.
Identifier always start with a lowercase letter.
A function application is a term, e.g.,
pair("rose", address("STREE", 12345)
.
A list is a term, e.g., [a, b,
c]
or [a, 1.25, "lost"]
.
A placeholder is a term, e.g., <int>
or add(<int>,<int>)
.
Term matching is used for several purposes in the ToolBus:
To determine which actions can communicate with each other.
For instance, a snd-msg
and a
rec-msg
can only communicate if their arguments
match.
To transfer information between sender and receiver.
To do case analysis, for instance, when receiving events from a tool.
Intuitively, the matching between two terms works as follows:
Two terms match if they are structurally identical.
For a value occurrence of a variable: use its current value.
For a result occurrence of a variable: assign the matched subterm of the other term to the variable (but make this only permanent if the overall match succeeds).
This illustrated in Figure 1.5, “Example of term matching”.
Before the match, two contexts are given. Each context associates some
variables with a value. For instance, Context 1 associates the value
3
with variable X
. For each
context a term is given and the challenge is to match these two terms
and to observe the effects on the two contexts. The matching of the two
terms can be understood as follows:
The top level function names are identical (both
f
) and both have the same number of arguments.
The left term and the right term match if their arguments
match.
The first argument in the left term is X
and 3
in the right term. Since,
X
has value 3
in Context 1,
they match.
The second argument in the left term is 4
and Z?
in the right term. By assigning
4
to Z
in Context 2 we achieve
a match.
The third argument in the left term is Y?
and 5
in the right term. Here we achieve a match
by assigning 5
to Y
in Context
1.
The fourth and last argument of both terms is
6
and thus matches.
The net result is that both terms match and that Context 1 and Context 2 are modified as shown at the bottom of the figure.
The ToolBus uses a type system that is a compromise between the safety of static checking and the flexibility of dynamic typing. Another objective of the type system is to provide sufficient information to enable the automatic generation of adapter code for tools. Type are defined as follows:
bool
, int
,
real
and str
are the types of
the elementary terms.
list
is the type of arbitrary lists.
list(
is
the type of lists with elements of type
Type
)Type
. For instance,
list(int)
is the type of lists of
integers.
Id
is the type of all terms with
function symbol Id
(this allows the
declaration of partial types). The type f
, thus
corresponds to the terms f
,
f(1)
, f("abc", 3)
and the
like.
Id(
is the type of terms with function symbol
Type
1,
...,
Type
n
)Id
and the given types
Type
1, ...,
Type
n
a
s argument types. For instance, f(int,str)
accepts f(3,"abc")
but not
f(3)
.
[Type
1, ...,
Type
n
]
is the type of a list of elements with the given types
Type
1, ...,
Typen.
For
instance, [int, str]
accepts [1,
"abc"]
, but not [1,2,3]
.
term
is the type of an arbitrary term. And
is used as escape from the more precise typing by the preceding
types.
Types are used in the following ways:
All variables have a type.
Types are statically checked whenever possible. Only in the
case of type term
, dynamic checks are
needed.
Types play a role during matching: a match can also fail if
the types of corresponding subterms are unequal. For instance, given
I
as int
variable,
S
as str
variable and
T
as term
variable,
f(13)
and f(I?)
will
match.
f(13)
and f(S?)
will
fail.
f(13)
and f(T?)
will
succeed.
A Tscript can define the following ingredients:
A process definition consisting of a process name, optional parameters and a process expression that describes the behaviour of this process.
A tool definition consisting of a tool name and some operational details, such as the command to execute when the tool is started.
A ToolBus configuration consisting of one or more process names (optionally followed by actual parameters) that will be created when the application is started. A Tscript may contain more than one ToolBus configuration.
An include file that contains another Tscript that will be literally included.
A constant definition.
A conditional that allows the conditional inclusion or exclusion of parts of the Tscript.
Before delving into the details of Tscripts, it is good to have
a look at the hello world application hello1.tb
shown in Example 1.1, “hello1.tb”.
Example 1.1. hello1.tb
process HELLOis
printf(“Hello world, my first Tscript!\n”)
toolbus(HELLO)
Notes:
Running this example will yield the following command line dialog:
1> toolbus hello1.tb Hello world, my first Tscript! 2>
Becoming more courageous, we show now a more ambitious Tscript
hello2.tb
in Example 1.2, “hello2.tb” that
does not print the hello string itself, but executes a tool to compute
it.
Example 1.2. hello2.tb
process HELLO islet H : hello,
S : str
in execute(hello, H?)
.
snd-eval(H, get_text) .
rec-value(H, text(S?)).
printf(S)
endlet tool hello is {command = “hello” }
toolbus(HELLO)
Notes:
All the Tscript primitives (including the ones that occur in these two simple examples) will now be described in more depth.
During execution, the ToolBus consists of a parallel composition of processes. The ToolBus configurations define the processes that are created at the start of the start of the application, but later on processes may die and new ones may be created.
Each process has a local state in the form of private local variables. These variables get their value through assignment and matching. They are only visible inside each process.
Processes are built-in up from atomic actions (detailed below) and atomic actions can be combined into process expressions using the following operators:
Sequential composition
.
First the actions in P
1 .
P
2
P
1are executed and then the
ones in
P
2.
Choice
. A
choice is made between the first action in
P
1 +
P
2P
1and the first
action in P
2.
This choice is based on two criteria:
An action to be selected must be enabled.
If more than one action is enabled, a random choice is made.
There are various ways in which an action can be enabled (this depends on the precise action):
An associated condition evaluates to true (see conditional and guarded command, below).
An associated timing constraint is true.
Required external tool results are available.
Communication conditions are satisfied.
Once the choice for the first action has been made all
remaining actions of the selected process expression
P
1 or
P
2 are executed
as well.
Parallel composition
.
The actions in
P
1 ||
P
2P
1and
P
2 are executed
in parallel. This means that the sequential order of the actions
in
P
1respectively
P
2 is respected
but that apart from this constraint the actions can be executed in
arbitrary order.
Iteration
.
P
1 *
P
2P
1 is executed
repeatedly, until an action of
P
2 is executed.
Execution then continues with the remaining actions of
P
2.
Conditional if
.
The test T
then
P
1 else
P
2 fiT
is evaluated and if the
result is true then
P
1 is executed,
otherwise P
2 is
executed. Note that the evaluation of the test does not count as a
separate atomic action; the test is effectively attached to the
first atom of
P
1respectively
P
2.
Guarded command if
. The test T
then P
fiT
is evaluated
and if the result is true then
P
1 is executed,
otherwise this command deadlocks.
As we have seen local variables play a key role in the execution
of Tscripts. They are defined using the let
construct:
let Var1 : Type1, ... in P endlet. Variables Var1, ... are
declared with respective type Type1, .... These variables act as
local variables during the execution of the process expression P.
P may contain other let
constructs.
Deadlock delta
. This
constant represents the process that cannot execute any further
steps. During execution deadlock is always avoided as long as this
is possible. A process that end in deadlock effectively terminates
and disappears.
Silent step tau
.
This constant represents one internal step in a process and
resemble a dummy statement in a conventional programming
language.
Print printf. An action for generating formatted output.
Assignment
. The term
V
:=
T
T
is evaluated as expression (using the
built-in functions) and the result is assigned to the local
variable V
.
Synchronous communication resembles an ordinary phone call: it involves two processes that can communicate at the same instant in time. In ToolBus terminology messages are used for synchronous communication. There are two primitives involved:
snd-msg
sends a message to another
process.
rec-msg
receives a message from another
process.
Two requirements have to be satisfied before communication can take place:
The arguments of snd-msg
and
rec-msg
match with each other.
In addition, snd-msg
respectively
rec-msg
are enabled in each process.
When communication takes place, the effects of the argument
matching is recorded in the local state of each process and both
continue execution. The observant reader may have noticed that sending
and receiving is actually symmetric: by way of result variables in the
arguments of snd-msg
and rec-msg
information may flow from sender to receiver and vice
versa.
Asynchronous communication resembles conventional e-mail: it involves one sending and zero or more receiving processes that read the communicated information at a later instant in time. In ToolBus terminology notes are used for asynchronous communication. There four primitives involved:
subscribe(
.
Subscribes a process to notes that match the term
T
)T
.
unsubscribe(
.
Unsubscribes a process from notes that match
T
)T
.
snd-note(
.
Broadcast the term T
)T
to all subscribed
processes. Effectively, T
is placed in
the private inbox of each subscribed process to be read at a later
moment.
rec-note(
.
Receive a note that matches T
)T
.
Effectively, the private inbox is searched for a note that matches
T
.
no-note(
.
There is no note that matches T
)T
in the
private inbox.
A process definition associates a name
Pnm
(optimally followed by parameters) with
a process expression P
. These process names
can be used in two ways in process expressions:
An inline process expression
:
Effectively, this amount to macro substitution:
Pnm
(...)Pnm
is replaced by the process
expression P
(after proper parameter
substitution).
A process creation
create(
: a completely new
process is created that runs in parallel with all other processes
currently running in the ToolBus. The process identifier of this
new process is assigned to Pnm
(...),
Pid
?)Pid
.
There two possible scenarios for a ToolBus tool. In scenario 1, the tool is executed from the ToolBus, the tool receives a number of evaluation requests and/or generates an number of events, and finally, the ToolBus decides to terminate the execution of the tool. A variation of scenario 1 is that the tool decides to disconnect from the ToolBus and continues execution disconnect from the ToolBus application. In scenario 2, the tool is executed separately and starts its cooperation by requesting a connection with the ToolBus. Once connected, it follows the same steps as in scenario 1. The following primitives achieve this (also see Figure 1.3, “Communication between ToolBus and tools” for the various communication patterns between ToolBus and tools):
execute(
: Execute a tool with
name Tnm
,
Tid
?)Tnm
. The result is a
tool identifier that is bound to
Tid
. Tool identifiers are unique; if
more than one instance of the same tool is executing they can be
distinguished via their tool identifier. There are two additional
constraints:
The Tscript should contain a tool definition for
Tnm
.
The variable Tid should have a type that corresponds
with the tool name, i.e., it should be declared as
. Why? Well this in
this way the implementation can track via the type of the tool
identifier in each tool request, which
tool it is and that information is essential for the automatic
generation of adapter code.Tid
:
Tnm
snd-terminate(
: terminates the execution
of the tool instance Tid
,
T
)Tid
. The term
T
contains a reason for the termination
and is usually printed by the tool on termination.
rec-connect(
: receive a connection
request for a tool with name Tnm
,
Tid
?)Tnm
.
rec-connect
is very similar to
execute
. The only difference is the initiating
party: for execute
the ToolBus and for
rec-connect
the tool.
rec-disconnect(
:
receive a disconnection request from a tool. It does not matter
whether the connection with the tool was originally established
via Tid
?)execute
or
rec-connect
.
snd-eval(
: send an evaluation
request to a tool. All value occurrences in
Tid
,
T
)T
are first replaced by their value
before sending T
to the tool. It is up
to the tool to interpret the term. The usual scenario is that the
outermost function symbol of T
is
identical to the name of a procedure in the tool and that
procedure is called. The ToolBus can only send one evaluation
request at a time. Only when the request is cancelled, or a value
is returned by the tool, the next request can be sent to the
tool.
rec-value(
: receive a value from a
tool in response to a previous Tid
,
T
)snd-eval
request. T
has to match the value from
the tool; this is useful for case distinctions. In many case, T
consists of a single result variable, or a is a term that contains
result variables.
snd-do(
: send an evaluation
request to a tool but do not expect a return value. Typically used
to implement printing or logging activities.Tid
,
T
)
rec-event(
: receive an event from a tool. Events need not be
handled one-by-one. The same tool may generate more than one event
provided that the value of argument
Tid
,
T
1,
...)T
1 differs.
T
1 thus serves
as identification for this event.
snd-ack-event(
:
acknowledge the completion of the handling of a previous event.
Since, Tid
,
)T
1T
1 is
identical to the
T
1 in a
preceding snd-event
and is used to identify
that event.
Time can play an important role in applications, be it as
ingredient in a protocol that prescribes certain time constraints, be
it as watchdog that certain operations are carried out in time. The
general approach in Tscripts is that a delay or timeout may be
attached to every atom action. Delays and timeouts may be relative to
the current time or they may be specified in absolute time. The
primitives are as follows (for arbitrary atomic action
A
):
Relative delay:
. Atom
A
delay(E
)A
can only become enabled after
E
seconds
have passed.
Absolute delay:
. Atom
A
abs-delay(Year
,
Month
, Day
,
Hour
, Min
,
Sec
)A
can only become enabled after the
specified absolute date and time.
Relative timeout:
. Atom
A
timeout(E
)A
is only enabled during the next
E
seconds.
Absolute timeout:
. Atom
A
abs-timeout(Year
,
Month
, Day
,
Hour
, Min
,
Sec
)A
is only enabled until the specified
absolute date and time.
Terms can occur in Tscripts on various locations. In the majority of cases these terms are used as such; only variables are replaced by their value but no further evaluation of terms take place. There are, however, two exceptions to this general rule. In three cases, terms are evaluated:
The test in if
and T
then
... fiif
.T
then
... else ... fi
The right-hand side of the assignment
.V
:=
T
In delays or timeouts.
The term is evaluated in a bottom-up manner, i.e., first arguments are evaluated and then the function is applied. Here are some examples:
not(true)
evaluates to
false
.
add(mul(2,3), 4)
evaluates to
10
.
greater(6,5)
evaluates to
true
.
first([9, 8, 7])
evaluates to
9
.
A detailed overview of all built-in functions is given in XXX. They can be summarized as follows:
Functions on Booleans:
not
, and
,
or
.
Functions on Integers:
add
, sub
,
mul
, div
,
mod
, less
,
less-equal
, greater
,
greater-equal
.
Functions on lists:
first
, next
,
get
, put
,
join
, member
,
subset
, diff
,
inter
, size
.
Miscellaneous functions:
equal
, not-equal
,
process-id
, process-name
,
current-time
, quote
.
We are now ready to have a look at some larger examples of Tscripts.
The calculator example illustrates how a calculator tool that can compute simple arithmetic expressions is shared by cooperating processes. The overall architecture is shown in Figure 1.6, “Architecture of the clock application”. The application consists of the following 5 processes:
CALC
: the calculator
process that regulates the access to the
calculator tool calc
, see
Example 1.4, “Process CALC
and tool
calc
”.
BATCH
: a batch process that uses the tool
batch
to read an expression from file, calculate
its value and write the result back to file, see Example 1.5, “Process BATCH
and tool
batch
”.
UI
: a user-interface process that uses the
tool ui to allow a user to enter an expression and get its value
back, see Example 1.6, “Process UI
and tool
ui
”. Observe that the processes
BATCH
and UI
are both
competing for the shared resource calculator (implemented by the
process CALC
and the tool
calc
).
LOG
: a logging process that maintains a log
of all calculations that have been performed by the application, see
Example 1.11, “Process LOG
and tool
log
”.
CLOCK
: a clock process that uses the tool
clock
to provide the current time, see Example 1.13, “Process CLOCK
and tool
clock
”.
The global structure of the Tscript
calc.tb
is sketched in Example 1.3, “Global structure of calc.tb
”.
Example 1.3. Global structure of calc.tb
process CALC is ... tool calc is ... See Example 1.4, “ProcessCALC
and toolcalc
” process BATCH is ... tool batch is ... See Example 1.5, “ProcessBATCH
and toolbatch
” process UI is ... See Example 1.6, “ProcessUI
and toolui
” process CALC-BUTTON is ... See Example 1.7, “ProcessCALC-BUTTON
” process LOG-BUTTON is ... See Example 1.8, “ProcessLOG-BUTTON
” process TIME-BUTTON is ... See Example 1.8, “ProcessLOG-BUTTON
” process QUIT-BUTTON is ... See Example 1.10, “ProcessQUIT-BUTTON
” process LOG is ... See Example 1.11, “ProcessLOG
and toollog
” process CLOCK is ... See Example 1.13, “ProcessCLOCK
and toolclock
” toolbus(CALC, BATCH, UI, LOG, CLOCK) See Example 1.14, “ToolBus configuration for calculator demo”
Example 1.4. Process CALC
and tool
calc
process CALC is let Tid : calc, E : str, V : term in execute(calc, Tid?).(
rec-msg(compute, E?) .
snd-eval(Tid, expr(E)) . rec-value(Tid, val(V?)) .
snd-msg(compute, E, V) . snd-note(compute(E, V))
)* delta
endlet tool calc is { command = “calc”}
Notes:
Example 1.5. Process BATCH
and tool
batch
The user-interface is shown in Figure 1.7, “The calc GUI” and behaves as follows:
When the user presses Figure 1.8, “Dialog resulting from CALC-BUTTON
”.
Pressing
displays all calculations so far.Pressing
displays the current time in a separate window.Pressing
end the application.Example 1.6. Process UI
and tool
ui
process UI is let Tid : ui in execute(ui, Tid?) . ( CALC-BUTTON(Tid) + LOG-BUTTON(Tid))* delta|| TIME-BUTTON(Tid) * delta
|| QUIT-BUTTON(Tid)
endlet tool ui is { command = wish-adapter -script calc.tcl” }
Notes:
Also observe the extensive use of named process
expressions like, for instance, CALC-BUTTON
to
give an high-level overview of the UI
process.
See Example 1.7, “Process CALC-BUTTON
”, Example 1.8, “Process LOG-BUTTON
”, Example 1.9, “Process TIME-BUTTON
”, and Example 1.10, “Process QUIT-BUTTON
” for their definitions.
Example 1.7. Process CALC-BUTTON
process CALC-BUTTON(Tid : ui) is let N : int, E : str, V : term in rec-event(Tid, N?, button(calc)) .snd-eval(Tid, get-expr-dialog) .
( rec-value(Tid, cancel)
+ rec-value(Tid, expr(E?)) .
snd-msg(compute, E) . rec-msg(compute, E, V?) . snd-do(Tid, display-value(V))
) . snd-ack-event(Tid, N)
endlet
Notes:
![]() |
The |
![]() |
Ask the |
![]() |
The user cancels the dialog; no further actions are needed. |
![]() |
The user has entered an expression. Communicate with the CALC process to compute a value. |
![]() |
Ask the |
![]() |
Acknowledge the event to the tool. |
Example 1.8. Process LOG-BUTTON
process LOG-BUTTON(Tid : ui) is let N : int, L : term in rec-event(Tid, N?, button(showLog)) . snd-msg(showLog) . rec-msg(showLog, L?) . snd-do(Tid, display-log(L)) . snd-ack-event(Tid, N) endlet
Example 1.9. Process TIME-BUTTON
process TIME-BUTTON(Tid : ui) is let N : int, T : str in rec-event(Tid, N?, button(showTime)) . snd-msg(showTime) . rec-msg(showTime, T?) . snd-do(Tid, display-time(T)) . snd-ack-event(Tid, N) endlet
Example 1.10. Process QUIT-BUTTON
process QUIT-BUTTON(Tid : ui) is rec-event(Tid, button(quit)) . shutdown("End of calc demo")
Example 1.11. Process LOG
and tool
log
process LOG is let Tid : log, E : str, V : term, L : term in subscribe(compute(<str>, <term>)) . execute(log, Tid?). ( rec-note(compute(E?, V?)) .snd-do(Tid, writeLog(E, V)) + rec-msg(showLog) .
snd-eval(Tid, readLog) . rec-value(Tid, history(L?)) . snd-msg(showLog, history(L)) ) * delta endlet
Notes:
![]() |
Receive a note about a computation that has taken place and log it. |
![]() |
Receive a message to show the value of the current log.
Retrieve it from the |
An alternative way to describe the LOG process is shown in
Example 1.12, “Process LOG1
: maintaining the log inside
the ToolBus”. Instead of running a separate log
tool, the process LOG1 maintains the log in a local process variable
TheLog. See the section called “Built-in functions” for a
description of the function join
that is used
this example.
Example 1.12. Process LOG1
: maintaining the log inside
the ToolBus
process LOG1 is let TheLog : list, E : str, V : term in subscribe(compute(<str>, <term>)) . TheLog := [] . ( rec-note(compute(E?, V?)) . TheLog := join(TheLog, [[E, V]]) + rec-msg(showLog) . snd-msg(showLog, TheLog) ) * delta endlet
Example 1.13. Process CLOCK
and tool
clock
process CLOCK is let Tid : clock, T : str in execute(clock, Tid?). ( rec-msg(showTime) . snd-eval(Tid, readTime) . rec-value(Tid, time(T?)) . snd-msg(showTime, T) ) * delta endlet
The complete ToolBus configuration that describes the start
of the calculator demo is shown in Example 1.14, “ToolBus configuration for calculator demo”. It starts the
mentioned processes in parallel and from that moment on user-interaction
and the activities of the BATCH process will drive the
execution.
In the classical auction, the auction master and all bidders are in the same room and interact with each other according to a fixed protocol. It is shown in Figure 1.9, “Classical auction”. The steps in the protocol are:
The auction master introduces a new item for sale and sets an initial price for it.
Next, bidders raise their hand and shout a new bid that is to be acknowledged by the auction master.
Step 2 is repeated as long as new bids come in.
When no new bids are being made, the auction master asks for "any higher bid?" and waits during a fixed period.
If no new bids come in during this period, the auction master declares the item for sale to be sold to the highest bidder.
If a new bid comes in during this period, the procedure continues with step 2.
A striking aspect of the classical auction is that the
auction master and the bidders can see each other.
This is a great communication and synchronization tool. In the case of a
distributed auction, auction master and bidders are
on different locations and can only communicate via the Internet, see
Figure 1.10, “Distributed auction”.
The communication and synchronization in a distributed auction has to be described explicitly and requires answers to questions like:
How are bids synchronized?
How to inform bidders about the highest bid?
How to decide when bidding is over and the item is to be sold?
How to handle bidders that come and go during the auction?
The auction application to be described answers these questions and has an architecture as shown in Figure 1.11, “Architecture of the auction example”.
The auction application consists of a variable number of
processes:
Auction
: the process Auction orchestrates
the complete auction and is controlled by the tool
master
that enables the auction master to offer
new items for sale and to monitor the progress of the
auction.
Bidder
: for each new bidder that enters the
auction a new process Bidder
and tool
bidder
are created. The tool bidder keep the user
informed and allows her or him to submit new bids.
The global structure of the Tscript auction.tb is sketched in
Example 1.15, “Global structure of auction.tb
”.
Example 1.15. Global structure of auction.tb
process Auction is ... See Example 1.16, “ProcessAuction
” tool master is ... process ConnectBidder is ... See Example 1.17, “ProcessConnectBidder
” tool batch is ... process OneSale is ... See Example 1.18, “ProcessOneSale
” process Bidder is ... See Example 1.22, “ProcessBidder
” toolbus(Auction)
Example 1.16. Process Auction
Example 1.17. Process ConnectBidder
Example 1.18. Process OneSale
process OneSale(Mid : master) is let Descr : str,InAmount : int,
Amount : int,
HighestBid : int,
Final : bool,
Sold : bool, Bid : bidder
in rec-event(Mid, new-item(Descr?, InAmount?)) .
HighestBid := InAmount . snd-note(new-item(Descr, InAmount)) .
Final := false . Sold := false . ( Here is the main logic of OneSale
) * if Sold then snd-ack-event(Mid, new-item(Descr, InAmount)) fi endlet
Notes:
![]() |
|
![]() |
|
![]() |
|
![]() |
|
![]() |
Two Boolean values control the logic of the bidding
process. |
![]() |
Bidder is a new bidder tool that has connected during the sale. |
![]() |
The auction master wants to initiate the sale of a new item. |
![]() |
Inform all connected bidders about the new item that is for sale. |
![]() |
The detailed logic is explained in Example 1.19, “Process |
Example 1.19. Process OneSale
, main logic
( if not(Sold) then ...fi + if not(or(Final, Sold)) then ...
fi + if and(Final, not(Sold)) then ...
fi + ConnectBidder(Mid, Bid?) ...
) * if Sold then ... fi
The main process logic consists of four parts:
![]() |
Handle one incoming bid, see Example 1.20, “Process |
![]() |
Start the "any higher bid" procedure, see Example 1.21, “Process OneSale, handling other cases”. |
![]() |
Sell the item when no further bids are received, see Example 1.21, “Process OneSale, handling other cases”. |
![]() |
Connect a bidder during the sale, see Example 1.21, “Process OneSale, handling other cases”. |
Example 1.20. Process OneSale
, handling one bid
( if not(Sold) then rec-msg(bid(Bid?, Amount?)) .snd-do(Mid, new-bid(Bid, Amount)) .
if less-equal(Amount, HighestBid) then snd-msg(Bid, rejected)
else HighestBid := Amount .
snd-msg(Bid, accepted) .
snd-note(update-bid(Amount)) .
snd-do(Mid, update-highest-bid(Bid, Amount)) .
Final := false fi fi + if not(or(Final, Sold)) then ... fi + if and(Final, not(Sold)) then ... fi + ConnectBidder(Mid, Bid?) ... ) * if Sold then ... fi
Notes:
Example 1.21. Process OneSale, handling other cases
( if not(Sold) then ... fi + if not(or(Final, Sold))then snd-note(any-higher-bid) delay(sec(10)) .
snd-do(Mid, any-higher-bid(10)) . Final := true
fi + if and(Final, not(Sold))
then snd-note(sold(HighestBid)) delay(sec(10)) .
Sold := true
fi + ConnectBidder(Mid, Bid?) .
snd-msg(Bid, new-item(Descr, HighestBid)) .
Final := false
) * if Sold then ... fi
Notes:
Example 1.22. Process Bidder
process Bidder(Bid : bidder) is let Descr : str, Amount : int, Acceptance : term in subscribe(new-item(<str>, <int>)) .subscribe(update-bid(<int>)) . subscribe(sold(<int>)) . subscribe(any-higher-bid) . ( ( rec-msg(Bid, new-item(Descr?, Amount?))
+ rec-note(new-item(Descr?, Amount?))
+ rec-disconnect(Bid)
. delta ) . snd-do(Bid, new-item(Descr, Amount)) .
( rec-event(Bid, bid(Amount?)) .
snd-msg(bid(Bid, Amount)) .
rec-msg(Bid, Acceptance?) . snd-do(Bid, accept(Acceptance)) . snd-ack-event(Bid, bid(Amount)) )* + rec-note(update-bid(Amount?)) .
snd-do(Bid, update-bid(Amount)) + rec-note(any-higher-bid) . snd-do(Bid, any-higher-bid) + rec-disconnect(Bid) . delta
) * rec-note(sold(Amount?)) .
snd-do(Bid, sold(Amount)) )* delta endlet
Notes:
Have you ever considered a (guitar) string that is attached at both ends and wondered how the movements of the strings can be simulated? Although this is a completely atypical application of the ToolBus, it is fun to do so we will delve into the details.
In mathematical physics, the vibrating string is described by the
so-called one-dimensional wave equation that describes a discrete
approximation of the continuous string. The discretization is achieved
by sampling the amplitude of the string at certain points
i
= 1, ... N
,
where N
is the number of points. The
amplitude at point i
at time
t
can now be described by
yi
+1(t
)
(see also Figure 1.12, “One-dimensional wave equation”) that is defined as
follows:
y
i
(t
+Δt
)
=
F
(y
i
(t
),
y
i
(t
-Δt
)
,
y
i
-1(t
),
y
i
+1(t
))
In other words, the amplitude at point
i
and time t
depends on:
the current amplitude,
the previous amplitude at this point,
the current amplitude of the left neighbour, and
the current amplitude of the right neighbour.
It also depends on the function F
defined as follows:
F
(z
1,z
2,z
3,z
4)
= 2z
1-
z
2 + (c
Δt
/Δx)2
(z
3 -
2z
1+
z
4)
where
Δx
is the
(small) interval between sampling points, and
c is a constant representing the propagation velocity of the wave.
After these preparations, we have to define the architecture of a ToolBus application that can simulate the behaviour of a string. The key idea is to use a separate ToolBus process to represent the behaviour of each sampling point. The architecture is shown in Figure 1.13, “Architecture of the wave example” and consists of the following processes and tools:
Process Pend
models an end point of the
string, see Example 1.26, “Process Pend
” .Two instances are used
to model the left and right end point.
Process P
models one sampling point, see
Example 1.27, “Process P
”. N
-1
instances are used to model all intermediate points.
The auxiliary process F
computes the
function F
discussed above, see Example 1.28, “Auxiliary process F
”.
Process makeWave
constructs
N
connected instances of processes
P
and two end points Pend
, see
Example 1.24, “Process MakeWave
”.
The tool display visualizes the simulation.
The global structure of the Tscript
wave.tb
is shown in Example 1.23, “Global structure of wave.tb
”.
Example 1.23. Global structure of wave.tb
process MakeWave ... See Example 1.24, “ProcessMakeWave
” process Pend ... See Example 1.26, “ProcessPend
” process P ... See Example 1.27, “ProcessP
” process F ... See Example 1.28, “Auxiliary processF
” toolbus(MakeWave(...))
Example 1.24. Process MakeWave
process MakeWave(N : int) is let Tid : display, Id : int, I : int, L : int, R : int in execute(display, Tid?) .snd-do(Tid, mk-wave(N)) . create(Pend(Tid, 0, 1), Id?).
L := sub(N,1) . create(Pend(Tid, N, L), Id?) . I := 1 .
if less(I, N) then L := sub(I, 1) . R := add(I, 1) . create(P(Tid, L, I, R, 1.0, 1.0), Id?) . I := add(I, 1) fi * shutdown("end") delay(sec(60))
endlet tool display is { command = "wish-adapter -script ui-wave.tcl"}
Notes:
![]() |
Execute the display tool and initialize it to show
|
![]() |
Create the two end points with index 0 and
|
![]() |
Create the intermediate points 1,...,
|
![]() |
Run the demo for one minute. |
Example 1.26. Process Pend
process Pend(Tid : display, I: int, NB
: int) is let W : real in ( rec-msg(NB, I, W?) || snd-msg(I, NB, 0.0) ||
snd-do(Tid, update(I, 0.0))
) * delta endlet
Notes:
Example 1.27. Process P
process P(Tid : display,L : int,
I : int,
R : int,
Dstart : real,
Estart : real
) is let AL : real, AR : real, D : real, D1 : real, E : real in D := Dstart . E := Estart . ( ( rec-msg(L, I, AL?)
|| rec-msg(R, I, AR?) || snd-msg(I, L, E)
|| snd-msg(I, R, E) || snd-do(Tid, update(I, E))
) . D1 := E . F(E, D, AL, AR, E?) .
D := D1 ) * delta endlet
Notes:
Example 1.28. Auxiliary process F
The ToolBus interpreter (toolbus) and all tools have some standard program arguments in common, but they have some specific arguments as well. In this section we describe all possible program arguments and the way to execute toolbus and tools.
ToolBus and tools have the following optional arguments in common:
-help
: prints a description of all
arguments of the ToolBus or tool.
-P
:
defines the "well known socket" port_name
port_name
to which all tools temporarily connect in order to set up their own
private socket that connects them permanently to the ToolBus
interpreter. When omitted, socket 8998 will be used in case of the C
ToolBus and a random free port in case of the ToolBus NG.
Not yet implemented.
Note that explicit arguments defining the sockets are only needed when several C ToolBus interpreters are running simultaneously on the same host machine.
The script_name
(see below) given as
argument to the ToolBus is always preprocessed by a preprocessor before
it is parsed as a Tscript. In this way, directives like, e.g.,
#define
, #include
and
#ifdef
can be used freely in Tscripts. The following
preprocessor arguments are accepted by the ToolBus
command:
-I
: append
directory dir
to the
list of directories searched for include files.dir
-D
: defines
name
with the string
name
"1"
as its definition.
D
:
defines name
=defn
with
name
as
definition.defn
Other arguments specific for the ToolBus command are:
The following arguments will probably be supported differently in ToolBusNG.
-viewer
: execute the ToolBus viewer that
enables step-by-step execution and inspection of the state of each
process state.
-gentifs
: only generate tool interfaces for
all tools used in the script in a language independent format. For a
script file named script.tb
the tool interfaces
are written to script.tifs
. Do not execute the
script.
Not yet implemented.
-fixed-seed
: use a fixed seed for the
random generator used by the interpreter for scheduling processes
and selecting alternatives in processes. By default, the random
generator is initialized with the current time the
ToolBus command is given. Using the
-fixed-seed
option makes the execution of the
script reproducible across multiple runs of the
ToolBus command.
Not yet implemented.
-S
:
any other argument is the name of the ToolBus script to be
interpreted.script_name
As an example, consider first
toolbus -Shello.tb
which
starts interpreting the script hello.tb
. Next,
consider
toolbus -Imy-include-dir -DCNT=33 -Swave.tb
which
searches the directory my-include-dir
for files
used in #include
directives in the script
wave.tb
and it will define the name
CNT
with value 33
. All occurrences
of CNT
in the script will be replaced by this value
before parsing it as a Tscript. Finally,
toolbus -gentifs -Shello.tb
produces
the tool interfaces file hello.tifs
.
This section needs some work.
Arguments specific for tools are:
-TB_HOST
: defines the host
machine host_name
on
which the ToolBus interpreter is running and to which the tool
should be connected. When omitted, the ToolBus interpreter should be
running on the same host as the tool.host_name
-TB_TOOL_NAME tool_name
: the tool name as
defined in the Tscript (added automatically, when a tool is executed
by the ToolBus).
-TB_TOOL_ID
:
internal tool identifier of this tool execution (added
automatically, when a tool is executed by the ToolBus).Id
The execution of a tool can start in two ways:
The tool is started by an execute
command
in the Tscript.
The initiative to execute the tool is taken outside the
ToolBus. This requires that the script contains a
rec-connect
for this particular tool.
When ToolBus and tool are running on different host machines, it is important to define the host machine on which the ToolBus interpreter is running when starting the execution of the tool. As an example, consider the hello application described in Example 1.2, “hello2.tb”. The hello tool will be executed by the ToolBus using the command
hello -P8998 -TB_HOST host1.institute.nl
when
running on machine host1.institute.nl
. Suppose, we
replace the explicit execute
in Example 1.2, “hello2.tb” by a rec-connect
as shown
in Figure~\ref{fig:hello3.tb}. We may then manually start the
hello tool by typing
hello
where
we use the default values for the input/output sockets and assume that
tool and ToolBus interpreter are both running on the same host (i.e.,
host1.institute.nl
). Starting the execution from
another host is achieved by typing (on, say,
host2.institute.nl
):
hello -P8998 -TB_HOST host1.institute.nl
There are some general issues to understand about ToolBus tools and we cover them here. First, the global structure of a tool is explained in the section called “The global structure of a ToolBus tool”. Next, we describe how tool adapters work in the section called “Adapters for tools and languages”. Finally, we cover in the section called “Automatic generation of tool interfaces” the automatic generation of tool interfaces that is needed for some tool implementation languages .
In its simplest form, a tool is a box connected via an input and an output port to a ToolBus. In the most general case, a tool has
one input port from the ToolBus to the tool and can receive tree structures (terms) via this port;
one output port from the tool to the ToolBus and can send terms to the ToolBus via this port;
zero or more term ports to receive terms from other sources;
zero or more character ports to receive character data from other sources.
This global, architectural, structure of a tool is shown in Figure 1.15, “Global tool organization”. With each input port, an event handler is associated that takes care of the processing of the data received via that port and is responsible for returning a result (if any). One tool may thus contain several event handlers. When a request is received, the following steps are taken:
The data received are parsed to check that they form a legal
ToolBus term T
. (If this is impossible, a
warning message is generated).
The event handler is called with T
as argument.
The event handler can do arbitrary processing needed to
decompose T
, to determine what has to be
done, and perform any desired computation.
The event handler returns either:
a legal ToolBus term representing a reply to be sent back to the ToolBus.
NULL
indicating that there is no
reply.
The global mode of operation of a tool is now:
receive data on any input port and respond to this by sending
some term (or NULL
) to the ToolBus; or
take the initiative to send a term to the ToolBus (typically to inform the ToolBus about some external event).
A tool is thus on the one hand a reactive engine that responds to a request from the ToolBus and returns the result back to the ToolBus in the form of a term (e.g., calculate the value of some expression), but on the other hand it can also take the initiative to send a term to the ToolBus (e.g., generate an event when a user pushes some button).
The main purpose of adapters is to act as small wrappers around existing programs or programming languages in order to transform them into tools that can be connected to the ToolBus. There exist two global strategies for constructing adapters:
The adapter and the program to be adapted are executed as separate (Unix) processes. This structure is sketched in Figure 1.16, “Two organizations of a tool adapter”. The advantage of this approach is that no access is needed to the source code of the program: it can remain a black box. Another advantage is that adapters may be reused for the adaptation of different programs. A possible disadvantage is some loss in efficiency.
In this category a further subdivision is possible:
The program is executed once as a child process of the
adapter and all
snd-eval
/snd-do
requests
are directed to this child process. The program can thus
maintain an internal state between requests.
The same program is executed as a child process of the
adapter for each
snd-eval
/snd-do
request.
A different program is executed as a child process of the
adapter for each
snd-eval
/snd-do
request.
Integrate the adapter and the software to be adapted into a single (Unix) process. This approach permits the most detailed adaptation of the program and is also the most efficient solution. This approach leads, however, to potentially less reusable adapters than the previous approach.
In order to achieve some uniformity, the current collection of adapters have the following optional program arguments in common:
-cmd
: the (default) program to be executed
by the adapter. All arguments of the adapter that follow
-cmd
are interpreted as the name and arguments of
the program to be executed.
All tool arguments, see the section called “Tool arguments”.
The interface code for each tool depends on the particulars
of the Tscript in which it is used. Changing the number of arguments in
an evaluation request to the tool, or adding a new request, requires
making changes to the interface code that are easily forgotten and
therefore error prone. Another observation is that the interface code
for different tools has a lot in common. An obvious solution to both
problems is to generate tool interfaces
automatically, given a Tscript. This generation process is shown in
Figure 1.17, “Automatic generation of tool interfaces” and consists of two steps:
Generate a language-independent description of all tool
interfaces used in the script. This amounts to a static analysis of
all tool communication in the script. It is achieved by using the
-gentifs
option of the ToolBus interpreter. For
instance,
toolbus -gentifs hello2.tb
will create a file hello2.tifs
containing the
tool interfaces.
Use the language independent interface description to generate a tool interface for a specific tool in a specific implementation language. The generator tifstoc exists for generating C tool interfaces. It is called as follows:
tifstoc -toolName
TifsFile
and
generates a file named
. For the
hello example, we would have, for instance: Name
.tif.c
tifstoc -tool hello hello2.tifs
The resulting file hello.tif.c
is shown in
Example 1.30, “The generated file hello.tif.c
”.
In Figure 1.17, “Automatic generation of tool interfaces” it is also shown how tool interface generators for other languages (e.g., Java, Cobol) fit into this scheme. In addition to tifstoc, we used to support the generation of Java interfaces by way of tifstojava. In the current ToolBus implementation, this is no longer necessary, see the section called “Writing ToolBus tools in Java”.
Although ToolBus tools can be implemented in many languages (including Java, C++, Tcl/Tk, ASF+SDF, and others) we start explaining how tools can be written in C. In other languages identical notions will be used with only minor adjustments to language-specific features and limitations. Writing tools in C amounts to:
ATerms: the essential data type that is used to exchange information between tool and TB
Add ref to ATerms
The global structure of a ToolBus tool, see the section called “The global structure of a ToolBus tool”.
The ToolBus Application Programmer's Interface , see the section called “The ToolBus API”.
Compiling ToolBus tools written in C, the section called “Compiling ToolBus tools written in C”.
Generating tool interfaces with {\tt tifstoc}, the section called “Automatic generation of tool interfaces”.
Each tool needs to include the file atb-tool.h
which defines some basic types as well as the set of library functions
available. It consists of
An include of <aterm1.h>
.
Defines ATBhandler
: the type of event
handlers.
Defines the prototypes of all library functions
When compiling tools, the library libATB.a
must
be specified in order to make the tool library available (using the
-lATB
option of the C compiler). It provides the
following functions:
ATBinit
: tool initialization, see the section called “ATBinit
”.
ATBconnect
: to connect the tool to the
ToolBus, the section called “ATBconnect
”.
ATBdisconnect
: to disconnect the tool from
the ToolBus, the section called “ATBconnect
”.
ATBeventloop
: a standard event loop for a
tool, see the section called “ATBeventloop”.
ATB
postEvent send an event to the ToolBus,
see the section called “ATB
postEvent”.
In the following section, we will describe these functions.
During the initialization of each tool, some preparations have to made before the tool can be properly connected to the ToolBus. These preparations include
Defining the name of the tool as it is known from a tool declaration in a Tscript.
Parsing standard program arguments that are passed to the tool when it is started.
Creating a pair of socket connections with a ToolBus interpreter.
Starting an event loop.
During execution of the event loop, the tool can either receive terms from the ToolBus or it can take the initiative to send terms to the ToolBus. It is thus possible for a tool to both respond to ToolBus requests and asynchronously send terms to the ToolBus.
The initialization of the ToolBus API is achieved by
int ATBinit(intargc
, char *argv
[], ATerm *bottomOfStack
).
This initializes the ToolBus API as well as the ATerm library that is used by it.
The standard program arguments that are passed (via
argc
and argv
)
are fully described in the section called “Executing ToolBus and tools”. Particularly
important is that the tool is initialized with a proper name. It
should be literally equal (including the case of letters) to a tool
name as appearing in a tool declaration in the Tscript. This is
important since the tool name will be used when the tool is connected
to the ToolBus. Note that ATBinit
also initializes
the ATerm library (hence the bottomOfStack
argument, see
Ref to Section \ref{ATinit}) in ATerm manual.
The return value indicates whether or not the ToolBus host could
be found: 0 indicates that all is well, and -1 indicates an error, in
which case the standard variable errno
of the C
run-time system is set to indicate which error.
A tool can be connected to the ToolBus using term
ports
that can be using for sending and receiving data
in the form of complete terms. Two aspects of term ports are
important: the input channel used for the actual data transfer and the
handler that takes care of processing input terms
when they arrive. The connection is established as
follows:
int ATBconnect(char *toolname
, char *host
, intport
, ATBhandlerh
);
Here,
toolname
is the tool name to be used,
host
is the machine where the ToolBus is
executing, port
is the file descriptor of
the channel to be used, and h
is the
handler to associated with this connection. If value
NULL
is passed as
toolname
or
host
, default values are used that are
taken from argv
that was passed to
ATBinit
. The same is true when
-1
is passed as value for
port
. The return value of
ATBconnect
is either -1
(failure) or a positive number (the connection succeeded and the
result is the file descriptor of the resulting socket connection with
the ToolBus). Handlers for term ports are functions from ATerm to
ATerm and have the type:
ATerm some_handler(intconn
, ATerminput
)
The
argument conn
is the connection along which
the input term was received and input
is
the actual term received. The term returned by the handler is the
reply to be sent to the ToolBus in response to this input event, or
NULL
if no reply is needed. In this fashion, an
arbitrary number of term input ports can be set up which will be read
in parallel: as soon as a term arrives at one of the ports the
associated handler is activated. A connection can be terminated as
follows:
void ATBdisconnect(int conn
)
where
conn
is a connection that has been created
earlier using ATBconnect
.
Many tools first establish a number of term ports and then enter an infinite loop that processes input events. The function
int ATBeventloop(void)
captures this idea. It never returns, unless something goes wrong. We can now give a skeleton that many tools have in common:
#include "my_tool.tif.c" ATerm my_tool_handler(int conn, ATerm input) { ... handle input and return a term or NULL ... } int main(int argc, char *argv[]){ ATerm bottomOfStack; ATBinit(argc, argv, &bottomOfStack); if(ATBconnect(NULL, NULL, -1, my_tool_handler) >= 0){ ATBeventloop(); }else{ fprintf(stderr, "my_tool: Could not connect to the ToolBus\n"); } ATBeventloop(); return 0; }
So far, we have seen primitives for tools that only receive terms from the ToolBus. In the case of events that are generated by a tool, a term needs to be sent from the tool to the ToolBus. This can be achieved using
int ATBpostEvent(intconn
, ATermterm
)
which
sends term
along the port
conn
. Failure is indicated by the return
value -1
. A typical usage is:
ATBpostEvent(conn, ATmake(button("ok"))).
Tools can also send synchroneous requests to the ToolBus with:
ATerm ATBpostRequest(int conn, ATerm request)
This call will send the given request to the ToolBus and return the response as soon as it arrives.
Tool programming amounts, in essence, to event driven
programming: most of the time a tool is awaiting the arrival of data
on one of its ports and when the data are there, a reply is sent to
the ToolBus by the handler associated with that port. In
computation-intensive tools, the need may arise to check for the
availability of incoming data from the ToolBus during computations. In
those cases, ATBeventloop may not offer enough flexibility. More
customized control flow can be achieved using the following functions.
Observe that these function are parameterized with a specific ToolBus
connection (as returned by ATBconnect
) and can be
used to handle situations where a single tool is connected with
more than one ToolBus.
Checking if there is input awaiting on a ToolBus connection is done by:
ATbool ATBpeekOne(int conn
)
This
function returns ATtrue
if incoming data from a
ToolBus are available on the connection
conn
.
Similarly, the availability of data on any connection may be checked by:
int ATBpeekAny(void)
If
input is waiting, the appropriate connection is returned. Otherwise
-1
is returned. The sequence of activities needed
for handling (once) the data available from a specific connection is
captured by the function
void ATBhandleOne(int conn
)
This
amounts to calling the handler associated with connection
conn
with the available data as input term.
Similarly, the data from any connection is
handled by
int ATBhandleAny(void)
which returns -1 if anything goes wrong.
Finally, the function
int ATBgetDescriptors(fd_set *set)
Gathers all ToolBus connection file descriptors in a single descriptor set. The return value indicates the maximum value of any descriptor in the set.
Given, the control flow primitives in the previous section, we can express various common control flow patterns.
The function ATBeventloop
can be expressed
with the primitives just introduced:
int ATBeventloop(void) { int conn; while(ATtrue) { n = ATBhandleAny(); if(n < 0) return -1; } }
Another style mixes the handling of input from the ToolBus, with other computations:
while(ATtrue) { if(n = ATBpeekAny() >= 0) /* if there is an incoming event */ ATBhandleOne(n); /* handle it */ else { ... /* perform other computation */ } }
In some tools, a mixture of passively awaiting input and
actively sending terms to the ToolBus can be seen. Using
ATBwriteTerm
, the most general global event loop of
a tool becomes:
while(ATtrue) { ... ATBwriteTerm(c1,e1); ...; ATBwriteTerm(cn,en); ... ATBhandleAny(); }
In other words, each iteration starts by sending zero or
more terms to the ToolBus (using ATBwriteTerm
) and
ends with processing one event coming from some port (using
ATBhandleAny
). The Tscript being used should, of
course, be able to receive such events.
When compiling a tool written in C the following questions should be answered:
Where is the include file aterm1.h
(or
aterm2.h
if you use the more sophisticated parts
of the ATerm library)?
Where is the include file
atb-tool.h
?
Where is the ATerm library
libATerm.a
?
Where is the ToolBus API library
libATB.a
?
Which other libraries are needed to compile the tool?
The answers to these questions are clearly system dependent. There are two strategies to answer them. Strategy 1: find the desired locations on your system and hard code them in the compilation command. This will lead to a call to the C compiler with the following arguments:
-I
dir-where-aterm1.h-is
-I
dir-where-ATB-tool.h-is
hello.c -o hello
-L
dir-where-libATerm.a-is
-lATerm
-L
dir-where-libATB.a-is
-lATB
other libraries.
Strategy 2: write a make file that encodes this information. As a result, the location information is hardwired in the make file rather than in a command that has to be repeated over and over again.
As already explained in the section called “Automatic generation of tool interfaces” tool interfaces can be
generated from a given Tscript for a given tool name. The ToolBus can
generate a language-independent .tifs
file, when it
is started with the -gentifs
option. In the case of
C, the command tifstoc generates a tool interface in
C for use with the ATerm library. The generated interface consists of
two files:
a C source file (hello2.tif.c
in the
example below), and
a C header file (hello2.tif.h
in the
example below).
In the header file a number of interface functions is declared, one for each element in the input signature of the tool. It is up to the writer of the tool to provide an implementation for these functions. The generated C file contains a handler function that analyzes incoming terms from the ToolBus, and delegates actual processing to the appropriate interface function.
We will use the Tscript hello2.tb
shown earlier
in Example 1.2, “hello2.tb” and describe all the steps needed
to write and compile the hello tool.
Using the command:
toolbus -gentifs hello2.tb
we generate a file called hello2.tifs
. It
contains information amount the interfaces for all tools that are used
in a given Tscript.
The -gentifs flag is not yet implemented.
Using the command:
tifstoc -tool hello test.tifs
we generate two files:
the header file hello.tif.h, see Example 1.29, “The generated header file
hello.tif.h
”.
the source file hello.tif.c
, see Example 1.30, “The generated file hello.tif.c
”.
Example 1.29. The generated header file
hello.tif.h
** * This file is generated by tifstoc. Do not edit! * Generated from tifs for tool 'hello' (prefix='') */ #ifndef _HELLO_H #define _HELLO_H #include <atb-tool.h> /* Prototypes for functions called from the event handler */ ATerm get_text(int conn); void rec_terminate(int conn, ATerm); extern ATerm hello_handler(int conn, ATerm term); extern ATerm hello_checker(int conn, ATerm sigs); #endif
Only the functions get_text
and
rec_terminate
together with a simple
main
function have to be implemented to build a
fully functional ToolBus tool.
Example 1.30. The generated file hello.tif.c
/** * This file is generated by tifstoc. Do not edit! * Generated from tifs for tool 'hello' (prefix='') */ #include "hello.tif.h" #define NR_SIG_ENTRIES 2 static char *signature[NR_SIG_ENTRIES] = {"rec-eval(<hello>,get_text)", "rec-terminate(<hello>,<term>)", }; /* Event handler for tool 'hello' */ ATerm hello_handler(int conn, ATerm term)
{ ATerm in, out; /* We need some temporary variables during matching */ ATerm t0; if(ATmatch(term, "rec-eval(get_text)")) { return get_text(conn); } if(ATmatch(term, "rec-terminate(<term>)", &t0)) { rec_terminate(conn, t0); return NULL; } if(ATmatch(term, "rec-do(signature(<term>,<term>))", &in, &out)) { ATerm result = hello_checker(conn, in); if(!ATmatch(result, "[]")) ATfprintf(stderr, "warning: not in input signature:\n\t%\n\tl\n", result); return NULL; } ATerror("tool hello cannot handle term %t", term); return NULL; /* Silence the compiler */ } /* Check the signature of the tool 'hello' */ ATerm hello_checker(int conn, ATerm siglist)
{ return ATBcheckSignature(siglist, signature, NR_SIG_ENTRIES); }
Notes:
As mentioned earlier, the only thing needed to implement the
actual hello tool, is the implementation of the two interface
functions get_txt
and
rec_terminate
, and the implementation of
main
to get things going. We will first take a look
at the initialization stuff that the main
function
of the hello tool has to do, see Example 1.31, “main
function of hello tool”.
Example 1.31. main
function of hello tool
#include <stdlib.h> #include "hello.tif.h" int main(int argc, char *argv[]) { ATerm bottomOfStack;ATBinit(argc, argv, &bottomOfStack);
if(ATBconnect(NULL, NULL, -1, testing_handler) >= 0) {
ATBeventloop();
} else { fprintf(stderr, "Could not connect to the ToolBus, giving up!\n"); return -1; } return 0; }
Notes:
Finally, we only need the implementation of the two interface
functions get_txt
and
rec_terminate
, see Example 1.32, “Implementation of interface functions of hello tool”
Example 1.32. Implementation of interface functions of hello tool
Now we will show how ToolBus tools can be implemented in Java. The
overall organization is shown in Figure 1.18, “Global organization of a tool implemented in Java”. The actual communication
between ToolBus and tool is taken care of by an instance of the class
ToolBridge
that takes care of low-level communication
details. The ToolBridge is used by AbstractTool
, an
abstract class that defines the possible interactions between ToolBus and
tool. The actual tool, in the figure MyTool
, extends
AbstractTool
, gives implementations for its abstract
methods, and implements tool-specific behaviour.
Compared to writing a tool in C, using Java is simpler because there is no need for generating a tool interface using a tif file.[2]
Before showing the Java implementation of the hello tool, we
will first explain the AbstractJavaTool class.
public abstract class AbstractJavaTool implements IOperations{ public AbstractJavaTool(){ ... }public void connect(String[] args) throws Exception{ ... }
public void connectDirectly(ToolBus toolbus, ClassLoader toolClassLoader, String toolName, int toolID) throws Exception{ ... }
public ToolBridge getToolBridge(){ ... }
public PureFactory getFactory(){ ... }
public void sendEvent(ATerm aTerm){ ... }
public ATerm sendRequest(ATerm request){ ... }
public void disconnect(ATerm aTerm){ ... }
public abstract void receiveAckEvent(ATerm aTerm);
public abstract void receiveTerminate(ATerm aTerm);
}
Notes:
The hello script from Example 1.2, “hello2.tb” can be used as is, except that the definition of the hello tool has to be changed to reflect the Java implementation:
tool hello is { kind = "javaNG" class = "toolbus.tool.java.hello.HelloTool"}
Example 1.33. Hello tool implemented in Java
package toolbus.tool.java.hello; import toolbus.adapter.java.AbstractJavaTool; import aterm.ATerm; import aterm.ATermFactory; public class HelloTool extends AbstractJavaTool{public HelloTool(){ super(); } public static void main(String[] args) throws Exception{ HelloTool helloTool = new HelloTool(); helloTool.connect(args);
} protected ATerm getText(){
ATermFactory factory = getFactory(); return factory.make("text(<str>)", "Hello world in Java!\n"); } public void receiveAckEvent(ATerm aTerm){ // Left blank intentionally. } public void receiveTerminate(ATerm msg){ System.out.print("rec-terminate received: " + msg); } }
Notes:
Writing ToolBus tools in Tcl is greatly simplified by the wish-adapter to be explained in the section called “wish-adapter”. Next, a small set of predefined Tcl functions is described that are always loaded by the wish-adapter and can be used in any Tcl script, see the section called “Predefined Tcl functions”. Finally, we present the Tcl version of the hello tool in the section called “The hello example in Tcl”.
The purpose of the wish-adapter is to execute Tcl/Tk's windowing shell wish as a tool. For instance,
wish-adapter -script calculator.tcl
executes wish as a TooBus tool and executes
the Tcl script calculator.tcl
.
In addition to the common tool arguments, wish-adapter has the following specific arguments:
-wish
:
Use Name
Name
rather than
wish as Tcl/Tk's windowing shell.
-lazy-exec
: Postpone execution of
wish until needed.
-script
: The Tcl script to be
executed.
-script-args
: The arguments for the Tcl
script to be executed. These arguments are available to the Tcl
script throught the variables argc
and
argv
.
Various communication patterns are supported by wish-adapter.
Communication is described here from the point of view of the ToolBus,
i.e., snd-
and rec-
mean,
respectively, send by ToolBus and receive by ToolBus. The
communication patterns are:
snd-do(
Tid
,
Fun
(A
1,
...,
A
n))
perform the Tcl function call
Fun
(A
1,
...,A
n).
Here Tid
is a tool identifier (as
produced by execute
or
rec-connect
) for an instance of the
wish-adapter.
snd-do(
Tid
,
Fun
(A
1,
...,A
n)):
perform the Tcl function call
Fun
(A
1,
...,A
n).
Here Tid
is a tool identifier (as
produced by execute
or
rec-connect
) for an instance of the
wish-adapter. Note that the function
Fun
must send an answer back to the
ToolBus (using TBsend "snd-eval(...)"
).
rec-value(
: the return value for a
previous evaluation request.Tid
,
Res
)
rec-event(Tid,
A
1,
...,A
n):
event generated by wish.
snd-ack-event(Tid
,
A
1):
acknowledgement of a previously generated event.
snd-terminate(Tid
,
A
1): terminate
execution of wish-adapter.
The command wish is executed once, an initial Tcl script is read, and all further requests are directed to this incarnation of wish. A small set of Tcl procedures is available for unpacking and packing ToolBus terms (see below).
The following Tcl functions are predefined and can be used freely in Tcl script executed via the wish-adapter:
TBstring
:
converts a Tcl string to a ToolBus string by surrounding it with
double quotes and escaping double quotes occurring inside
Str
Str
.
TCLstring
:
converts a ToolBus string into a Tcl string by removing
surrounding double quotes.Str
TBlist
:
converts a Tcl list to a ToolBus list by separating the elements
with commas and surrounding the list by curly braces.List
TBerror
:
constructs an error message that can be sent to the
ToolBus.Msg
TBsend
:
send Trm
Trm
back to the ToolBus.
TBevent
:
send event Event
Event
to the ToolBus.
TBrequire
: check that the Tcl
code for ToolName
ProcName
Nargs
ToolName
contains a procedure
declaration for ProcName
with
Nargs
formal parameters. This function
is mainly used by the wish-adapter to check
compatibility of the Tcl code with the expected input signature of
the tool.
All communication between wish-adapter and a tool written in Tcl is done via standard input/output. Only use the standard error stream for print statements in the Tcl script, since using standard output will disrupt the communication with the ToolBus.
Writing the hello tool in Tcl requires two steps:
Write the required Tcl code hello.tcl
.
The result is shown in Example 1.34, “hello.tcl: the hello tool in Tcl”.
Replace hello's tool definition in
hello2.tb
(see Example 1.2, “hello2.tb”) by:
tool hello is {command = "wish-adapter -script hello.tcl"}
Example 1.34. hello.tcl: the hello tool in Tcl
# hello.tcl -- hello tool in Tcl/Tk proc get-text {} { TBsend "snd-value(text(\"Hello World tool in Tcl!\n\"))" } proc rec-terminate { n } { exit }
A Python adapter is only available for older versions of the ToolBus. It is currently not supported.
A Tscript may contain directives like, e.g.,
#define
, #include
and
#ifdef
that are replaced by a preprocessor similar to
the C preprocessor. We summarize the most frequently used
directives:
#define
causes the
preprocessor to replace all occurrences of
Identifier
Token-sequence
Identifier
by
Token-sequence
.
#include
"
will be replaced by
the entire contents of the named file.Filename
"
#ifdef
and #ifndef
can
be used for the conditional incorporation or exclusion of parts of a
script.
The syntax of Tscripts (without preprocessor directives) is as follows:
This definition is slightly out-of-date.
exports sorts BOOL NAT INT SIGN EXP UNSIGNED-REAL REAL STRING ID NAME VNAME BSTR TERM TERM-LIST VAR GEN-VAR TYPE ATOM ATOMIC-FUN PROC PROC-APPL FORMALS TIMER-FUN FEATURE-ASG FEATURES TB-CONFIG DEF T-SCRIPT lexical syntax [ \t\n] -> LAYOUT "%%" ~[\n]* -> LAYOUT [0-9]+ -> NAT NAT -> INT SIGN NAT -> INT [+\-] -> SIGN [eE] NAT -> EXP [eE] SIGN NAT -> EXP NAT "." NAT -> UNSIGNED-REAL NAT "." NAT EXP -> UNSIGNED-REAL UNSIGNED-REAL -> REAL SIGN UNSIGNED-REAL -> REAL [a-z][A-Za-z0-9\-]* -> ID "\"" ~[\"]* "\"" -> STRING [A-Z][A-Za-z0-9\-]* -> NAME [A-Z][A-Za-z0-9\-]* -> VNAME [a-z][a-z\-]* -> ATOMIC-FUN delay -> TIMER-FUN abs-delay -> TIMER-FUN timeout -> TIMER-FUN abs-timeout -> TIMER-FUN context-free syntax true -> BOOL false -> BOOL BOOL -> TERM INT -> TERM REAL -> TERM STRING -> TERM TERM -> TYPE VNAME -> VAR VNAME ":" TYPE -> VAR VAR -> GEN-VAR VAR "?" -> GEN-VAR GEN-VAR -> TERM "<" TERM ">" -> TERM ID -> TERM ID "(" TERM-LIST ")" -> TERM {TERM ","}* -> TERM-LIST "[" TERM-LIST "]" -> TERM NAME -> VNAME ATOMIC-FUN "(" TERM-LIST ")" -> ATOM delta -> ATOM tau -> ATOM create "(" NAME "(" TERM-LIST ")" "," TERM ")" -> ATOM ATOM TIMER-FUN "(" TERM ")" -> ATOM VNAME ":=" TERM -> ATOM ATOM -> PROC PROC "+" PROC -> PROC {left} PROC "." PROC -> PROC {right} PROC "||" PROC -> PROC {right} PROC "*" PROC -> PROC {left} "(" PROC ")" -> PROC {bracket} if TERM then PROC else PROC fi -> PROC if TERM then PROC fi -> PROC execute(TERM-LIST) -> PROC let {VAR ","}* in PROC endlet -> PROC NAME -> PROC-APPL NAME "(" TERM-LIST ")" -> PROC-APPL PROC-APPL -> PROC "(" {GEN-VAR ","}* ")" -> FORMALS -> FORMALS process NAME FORMALS is PROC -> DEF ID "=" STRING -> FEATURE-ASG "{" { FEATURE-ASG ";"}* "}" -> FEATURES tool ID FORMALS is FEATURES -> DEF toolbus "("{PROC-APPL ","}+ ")" -> TB-CONFIG DEF* TB-CONFIG -> T-SCRIPT priorities PROC "*" PROC -> PROC > PROC "." PROC -> PROC > PROC "+" PROC -> PROC > PROC "||" PROC -> PROC
Tscripts provide a limited form of built-in functions that are summarized here. Recall that built-in functions are only evaluated at the following syntactic positions in a Tscript:
The right-hand side of an assignment.
The test in an if-then or if-then-else construct.
The expression in time-related constructs.
Table 1.1. Boolean functions
Function | Result type | Description |
---|---|---|
not(<bool>1) | <bool> | ¬
<bool>1 |
and(<bool>1,
<bool>2) | <bool> | <bool> 1
∧
<bool> 2 |
or(<bool>1,
<bool>2) | <bool> | <bool> 1
∨
<bool> 2 |
equal(<term>1,
<term>2) | <bool> | <term>1
=
<term>2 |
not-equal(<term>1,
<term>2) | <bool> | <term> 1
≠
<term> 2 |
Table 1.2. Integer functions
Function | Result type | Description |
---|---|---|
add(<int>1,
<int>2) | <int> | <int>1
+
<int>2 |
sub(<int>1,
<int>2) | <int> | <int>1
-
<int>2 |
mul(<int>1,
<int>2) | <int> | <int>1
×
<int> 2 |
div(<int>1,
<int>2) | <int> | <int>1
/
<int>2 |
mod(<int>1,
<int>2) | <int> | <int>1
mod
<int>2 |
abs(<int>1) | <int> | |
<int>1
| |
less(<int>1,
<int>2) | <bool> | <int>1
<
<int>2 |
less-equal(<int>1,
<int>2) | <bool> | <int>1
≤
<int>2 |
greater(<int>1,
<int>2) | <bool> | <int>1
>
<int>2 |
greater-equal(<int>1,
<int>2) | <bool> | <int>1
≥
<int>2 |
Table 1.3. Real functions
Function | Result type | Description |
---|---|---|
radd(<real>1,
<real>2) | <real> | <real>1
+
<real>2 |
rsub(<real>1,
<real>2) | <real> | <real>1
-
<real>2 |
rmul(<real>1,
<real>2) | <real> | <real>1
×
<real>2 |
rdiv(<real>1,
<real>2) | <real> | <real>1
/
<real>2 |
mod(<real>1,
<real>2) | <real> | <real>1
mod
<real>2 |
rabs(<real>1) | <real> | |
<real>1
| |
rless(<real>1,
<real>2) | <bool> | <real>1
<
<real>2 |
rless-equal(<real>1,
<real>2) | <bool> | <real>1
≤
<real>2 |
rgreater(<real>1,
<real>2) | <bool> | <real>1
>
<real>2 |
rgreater-equal(<real>1,
<real>2) | <bool> | <real>1
≥
<real>2 |
Table 1.4. Goniometric functions
Function | Result type | Description |
---|---|---|
sin(<real>1) | <real> | sin(<real>1 ) |
cos(<real>1) | <real> | cos(<real>1 ) |
atan(<real>1) | <real> | tan-1(<real>1 )
in the range [-π/2, π/2] |
atan2(<real>1,
<real>2) | <real> | tan-1(<real>1 /<real>2 )
in the range [-π, π] |
exp(<real>1) | <real> | e<real>1 |
log(<real>1) | <real> | natural logarithm
ln(<real>1 ),
with <real>1
> 0 |
log10(<real>1) | <real> | base 10 logarithm
log10(<real>1 ),
with <real>1
> 0 |
sqrt(<real>1) | <real> | √<real>1 ,
with <real>1
≥ 0 |
Table 1.5. Functions on lists
Function | Result type | Description |
---|---|---|
first(<list>1) | <term> | First element of
<list>1 ; The
empty list [] when applied to non-list or
empty list. |
next(<list>1) | <list> | Remaining elements of
<list>1 . |
join(<term>1,
<term>2) | <list> | Concatenation of
<term>1 and
<term>2 . When
both arguments are lists their elements are spliced into a new
list. A non-list argument is included as single element in the
new list. |
size(<list>1) | <int> | The number of elements in
<list>1 . |
Table 1.6. Functions on lists as arrays
Function | Result type | Description |
---|---|---|
index(<list>1,
<int>1) | <term> | The
<int>1 -th
element of
<list>1 , if it
exists; otherwise [] . |
replace(<list>1,<int1>,<term>1) | <list> | If the
<int>1 -the
element exists, replace it by
<term>1 and
returned the modified list; otherwise return
<list>1
unmodified. |
Table 1.7. Functions on lists as symbol tables
Function | Result type | Description |
---|---|---|
get(<list>1,
<term>1) | <term> | If
<list>1 contains
a pair [<term>1,
<term>1'] then return
<term>1' ;
otherwise []. |
put(<list>1,<term>1,<term>2) | <list> | If
<list>1 contains
a pair [<term>1,
<term>1'] then replace
it by [<term>1,
<term>2] ; otherwise add
a new pair [<term>1,
<term>2] to
<list>1. |
Table 1.8. Functions on lists as multi-sets
Function | Result type | Description |
---|---|---|
member(<term>1,
<list>1) | <bool> | <term>1
∈
<list>2
(membership in multi-set) |
subset(<list>1,<list>2) | <bool> | <list>1
⊆
<list>2
(subset on multi-set) |
diff(<list>1,<list>2) | <list> | <list>1
−
<list>2
(difference on multi-set) |
inter(<list>1,<list>2) | <list> | <list>1
∩
<list>2
(intersection on multi-set) |
Table 1.9. Functions on ATerms
Function | Result type | Description |
---|---|---|
is-bool(<term>) | <bool> | If <term> is of type
bool then true ;
otherwise false . |
is-int(<term>) | <bool> | If <term> is of type
int then true ;
otherwise false . |
is-real(<term>) | <bool> | If <term> is of type
real then true ;
otherwise false . |
is-str(<term>) | <bool> | If <term> is of type
str then true ;
otherwise false . |
is-bstr(<term>) | <bool> | If <term> is of type
bstr then true ;
otherwise false . |
is-appl(<term>) | <bool> | If <term> is an application
then true ; otherwise
false . |
is-list(<term>) | <bool> | If <term> is a list then
true ; otherwise
false . |
is-empty(<term>) | <bool> | If <term> is equal to
[] then true ;
otherwise false . |
is-var(<term>) | <bool> | If <term> is a variable then
true ; otherwise
false . |
is-var(<term>) | <bool> | If <term> is a variable then
true ; otherwise
false . |
is-result-var(<term>) | <bool> | If <term> is a result
variable then true ; otherwise
false . |
is-formal(<term>) | <bool> | If <term> is a formal
variable then true ; otherwise
false . |
fun(<term>) | <str> | If <term> is a function
application then its function symbol; otherwise
"" . |
args(<term>) | <list> | If <term> is a function
application then its argument; otherwise
[] . |
Table 1.10. Time-related functions
Function | Result type | Description |
---|---|---|
current-time | <list> | Six-tuple describing the current absolute time |
sec(<int>) | <int> | Convert <int> into seconds |
In the following two sections all primitives are summarized that can occur in a Tscript.
Table 1.12. Process-related primitives in Tscripts
Primitive | Synopsis | See |
---|---|---|
delta | Inaction (deadlock) | |
tau | Internal step | |
P 1
+
P 2 | Choice between
P 1 and
P 2 | |
P 1
.
P 2 | P 1
followed by
P 2 | |
P 1
||
P 2 | P 1
parallel with
P 2 | |
P 1
*
P 2 | Repeat
P 1 until
P 2 | |
if | Guarded command | |
if | Conditional | |
create( | Create new process | |
| Assign T ( seen as
expression) to V | |
snd-msg( | Send synchronous message | |
rec-msg( | Receive a synchronous message | |
snd-note( | Broadcast an asynchronous note | |
rec-note( | Receive an asynchronous note | |
no-note( | No note available | |
subscribe( | Subscribe to notes | |
| Relative delay of atom execution | |
| Absolute delay of atom execution | |
| Relative timeout of atom execution | |
| Absolute timeout of atom execution | |
shutdown( | Terminate ToolBus application | |
printf( | Print terms according to format string S | |
read( | Give prompt
T 1and read
term that should match with
T 2 | |
process
| Define process
| |
let | Declare local variables in
P | |
ToolBus( | Define initial ToolBus process configuration |
Table 1.13. Tool-related primitives in Tscripts
Primitive | Synopsis | See |
---|---|---|
rec-connect( | Receive connection request from tool | |
rec-disconnect( | Receive disconnection request from tool | |
execute( | Execute a tool | |
snd-terminate( | Terminate execution of a tool | |
snd-eval( | Send evaluation request to tool | |
snd-cancel( | Cancel previous evaluation request | |
rec-value( | Receive answer to evaluation request | |
snd-do( | Send evaluation request to tool (no return value) | |
rec-event( | Receive event from tool | |
snd-ack-event( | Acknowledge previous event from tool | |
tool | Define tool Tnm | |
host =
| Host feature in tool definition | |
command =
| Command feature in tool definition |
The first generation ToolBus is described in [BK94]. In addition to the design, the complete C implementation is discussed in detail. The second generation ("discrete time") ToolBus includes timing primitives as well as built-in functions. It has been formally described using ASF+SDF, see [BK95] and [BK98]. In [Oli00] a framework for the debugging of ToolBus applications is presented. Initial thoughts about a next generation ToolBus were published in [dJK03]. [dJ07] describes architectural aspects of ToolBus-based applications.
Add: theses of Peter Heibrink, Arnold Lankamp, Dennis Hendriks.
[BK94] The toolbus: a component interconnection architecture. Technical ReportP9408. University of Amsterdam, Programming Research Group. 1994.
[BK95] The discrete time toolbus. Technical ReportP9502. University of Amsterdam, Programming Research Group. 1995.
[BK98] 205--229. The discrete time ToolBus -- a software coordination architecture. Science of Computer Programming. 2-3. July 1998.
What do we do with the other adapters?
Describe current viewer.
Describe console commands.
Do we describe the global structure of the Java implementation (or partially refer to online docs)?
[1] By ``processes'' we mean here computational activities inside the ToolBus as opposed to, for instance, processes at the operating system level. When confusing might arise, we will call the former ``ToolBus processes'' and the latter ``operating system level tasks''.
[2] This is due to the use of Java reflection in the class ToolBridge. In older versions, a generation step was also needed for Java tools.