Treo: Textual Syntax for Reo Connectors

06/26/2018 ∙ by Kasper Dokter, et al. ∙ Centrum Wiskunde & Informatica 0

Reo is an interaction-centric model of concurrency for compositional specification of communication and coordination protocols. Formal verification tools exist to ensure correctness and compliance of protocols specified in Reo, which can readily be (re)used in different applications, or composed into more complex protocols. Recent benchmarks show that compiling such high-level Reo specifications produces executable code that can compete with or even beat the performance of hand-crafted programs written in languages such as C or Java using conventional concurrency constructs. The original declarative graphical syntax of Reo does not support intuitive constructs for parameter passing, iteration, recursion, or conditional specification. This shortcoming hinders Reo's uptake in large-scale practical applications. Although a number of Reo-inspired syntax alternatives have appeared in the past, none of them follows the primary design principles of Reo: a) declarative specification; b) all channel types and their sorts are user-defined; and c) channels compose via shared nodes. In this paper, we offer a textual syntax for Reo that respects these principles and supports flexible parameter passing, iteration, recursion, and conditional specification. In on-going work, we use this textual syntax to compile Reo into target languages such as Java, Promela, and Maude.

READ FULL TEXT VIEW PDF
POST COMMENT

Comments

There are no comments yet.

Authors

page 1

page 2

page 3

page 4

This week in AI

Get the week's most popular data science and artificial intelligence research sent straight to your inbox every Saturday.

1 Introduction

The advent of multicore processors has intensified the significance of coordination in concurrent applications. A programmer tackles the coordination concern of an application by specifying a (usually implicit) protocol that defines all possible permissible interactions among different active components of the application. Depending on the language used, programmers define their protocols at different levels of abstraction. A threading library, for instance, generally offers only basic synchronization primitives, such as locks and semaphores, that can be inserted into imperative code to ensure execution follows an implicitly defined protocol. Exogenous coordination languages offer syntax to programmers to explicitly define their interaction protocols at a high level of abstraction.

Reo [2, 3] is an example of such a coordination language that defines an interaction protocol as a connector: a graph-like structure that enables (a)synchronous data flow along its edges (cf., Figure 1). Each edge, called a channel, has a user-defined type and two channel ends. The type determines the behavior of the channel, specified as a constraint on the flows of data at its two ends. This constraint is expressed in a used-defined semantic sort, such as timed data streams, constraint automata, or coloring semantics [14]. A channel end is either a source end through which the channel accepts data, or a sink end through which the channel offers data. Multiple channel ends coincident at a vertex of the connector together form a node. Nodes have predefined ‘merge-replicate’ behavior: a node repeatedly accepts a datum from one of its coincident sink ends, chosen non-deterministically, and offers a copy of that datum through every one of its coincident source ends.

Tools for Reo have been implemented as a collection of Eclipse plugins called the ECT [9]. The main plugin in this tool set consists of a graphical editor that allows a user to draw a connector on a canvas. The graphical editor has an intuitive interface with a flat learning curve. However, it does not provide constructs to express parameter passing, iteration, recursion, or conditional construction of connector graphs. Such language constructs are more easily offered by familiar programming language constructs in a textual representation of connectors.

In the context of Vereofy (a model checker for Reo), Baier, Blechmann, Klein, and Klüppelholz developed the Reo Scripting Language (RSL) and its companion language, the Constraint Automata Reactive Module Language (CARML) [4, 20]. RSL is the first textual language for Reo that includes a construct for iteration, and a limited form of parameter passing. Primitive channels and nodes are defined in CARML, a guarded command language for specification of constraint automata. Programmers then combine CARML specified constraint automata as primitives in RSL to construct complex connectors and/or complete systems. In contrast to the declarative nature of the graphical syntax of Reo, RSL is imperative.

Jongmans developed the First-Order Constraint Automata with Memory Language (FOCAML) [13], a textual declarative language that enables compositional construction of connectors from a (pre-defined set of) primitive components. As a textual representation for Reo, however, FOCAML has poor support for its primary design principle: Reo channels are user-defined, not tied to any specific formalism to express its semantics, and compose via shared nodes with predefined merge-replicate behavior. Although FOCAML components are user-defined, FOCAML requires them to be of the same predefined semantic sort (i.e., constraint automata with memory [5]). The primary concept of Reo nodes does not exist in FOCAML, which forces explicit construction of their ‘merge-replicate’ behavior in FOCAML specifications.

Jongmans et al. have shown by benchmarks that compiling Reo specifications can produce executable code whose performance competes with or even beats that of hand-crafted programs written in languages such as C or Java using conventional concurrency constructs [19, 15, 17, 16, 18]. A textual syntax for Reo that preserves its declarative, compositional nature, allows user-defined primitives, and faithfully complies with the semantics of its nodes can significantly facilitate the uptake of Reo for specification of protocols in large-scale practical applications.

In this paper, we introduce Treo, a declarative textual language for component-based specification of Reo connectors with user-defined semantic sorts and predefined node behavior. We recall the basics of Reo (Section 2). We describe the structure of a Treo file by means of an abstract syntax (Section 3). In Listing LABEL:lst:concretesyntax, we provide a concrete syntax of Treo as an ANTLR4 grammar [23]. In on-going work, we currently use Treo to compile Reo into target languages such as Java, Promela, and Maude [24]. The construction of the Treo compiler is based on the theory of stream constraints [8].

In order to preserve the agnosticism of Reo regarding the concrete semantics of its primitives, Treo uses the notion of user-defined semantic sorts. A user-defined semantic sort consist of a set of component instances together with a composition operator , a substitution operator , and a trivial component (Section 4). The composition operator defines the behavior of composite components as a composition of its operands. The substitution operator binds nodes in the interface or passes values to parameters.

For a given semantic sort, we define the meaning of abstract Treo programs (Section 5). Treo is very liberal with respect to parameter values. A component definition not only accepts the usual (structured) data as actual parameters, but also other component instances and other component definitions. Among other benefits, this flexible parameter passing supports component sharing, which is useful to preserve component encapsulation [6, Figure 2].

A given semantics sort may possibly distinguish between inputs and outputs. Thus, not all combinations of components may result in a valid composite component. For example, the composition may not be defined, if two components share an output. In Treo, however, it is safe to compose components on their outputs, because, complying with the semantics of Reo, the compiler inserts special node components to ensure well-formed compositions (Section 6).

We conclude by discussing related work (Section 7), and pointing out future work (Section 8).

2 Reo

We briefly recall the basics of the Reo language and refer to [2] and [3] for further details. Reo is a language for specification of interaction protocols, originally proposed with a graphical syntax. A Reo program, called a connector, is a graph-like structure whose edges consist of channels that enable synchronous and asynchronous data flow and whose vertices consist of nodes that synchronously route data among multiple channels. Each channel has a type and two channel ends. Each channel end is either a source end, through which the channel accepts data, or a sink end, through which the channel offers data. The type of a channel completely defines the behavior of the channel in some user-defined semantic sort. Reo is agnostic regarding the semantic sort that expresses the behavior of its channel types, so long as the semantic sort preserves Reo’s compositional construction principle (cf., Definition 4.1). Table 1 shows some frequently used channels and an example node together with an informal description of their behavior.

A Sync channel accepts datum from its source end , when its simultaneous offer of this datum at its sink end succeeds.
A SyncDrain channel simultaneously accepts data from both its source ends and and loses the data.
An empty FIFO accepts data from its source end and becomes a full FIFO. A full FIFO offers its stored data at its sink end and, when its offer succeeds, it becomes an empty FIFO again.
A Reo node accepts a datum from one of its coincident sink ends ( or ), when its simultaneous offer to dispense a copy of this datum through every one of its coincident source ends ( and ) succeeds.
Table 1: Informal description of the behavior of nodes and of some channels in Reo.

The key concept in Reo is composition, which allows a programmer to build complex connectors out of simpler ones. For example, using the channels in Table 1, we can construct the Alternator connector, for , as shown in Figure 1. For , the Alternator consists of four nodes (, , , and ) and four channels, namely a SyncDrain channel (between and ), two Sync channels (from to , and from to ), and a FIFO channel (from to ).

Figure 1: Construction of the Alternator Reo connector, for .

The behavior of the Alternator connector is as follows. Suppose that the environment is ready to offer a datum at each of the nodes and , and ready to accept a datum from node . According to Table 1, nodes and both offer a copy of their received datum to the SyncDrain channel. The SyncDrain channel ensures that nodes and accept data from the environment only simultaneously. The Sync channel from to ensures that node simultaneously obtains a copy of the datum offered at . By definition, node either accepts a datum from the connected Sync channel or it accepts a datum from the FIFO channel (but not from both simultaneously), and offers this datum immediately to the environment. Because the FIFO is initially empty, has no choice but to accept and dispense the datum from . Simultaneously, the Sync channel from to ensures that the value offered at is stored in the FIFO buffer. In the next step, the environment at node has no choice but to retrieve the datum in the buffer, after which the behavior repeats.

3 Treo syntax

We now present a textual representation for the graphical Reo connectors in Section 2. Table 2 shows the abstract syntax of Treo.

Table 2: Abstract syntax of Treo, with start symbol (a source file), and terminal symbols for imports (), primitive components (), functions (), relations (), names (), and the empty list (). The bold vertical bar in is just text.

We introduce the symbols in the abstract syntax by identifying them in some concrete examples. These concrete examples are Treo programs that can be parsed using the concrete Treo syntax shown in Listing LABEL:lst:concretesyntax.

1grammar Treo;
2file    : sec? imp* assg* EOF;
3sec     : section name ’;’ ;
4imp     : import name ’;’ ;
5assg    : ID defn ;
6defn    : var | params? nodes comp ;
7comp    : defn vals? args | var | ’{’ atom+ ’}’ | ’{’ comp* (’|’ pred)? ’}’
8        | for ’(’ ID in list ’)’ comp
9        | if ’(’ pred ’)’ comp (’else ’(’ pred ’)’ comp)* (’else comp)? ;
10atom    : STRING ; /* Example syntax for primitive components */
11pred    : true | false | ’(’ pred ’)’ | var in list
12        | term op=(’<=’ | ’<’ | ’>=’ | ’>’ | ’=’ | ’!=’) term
13        | var | forall ID in list ’:’ pred | exists ID in list ’:’ pred
14        | not pred | pred (’and’|’,’) pred | pred or pred | pred implies pred ;
15term    : var | NAT | BOOL | STRING | DEC | comp | defn | list | len(’ term ’)’
16        | ’(’ term ’)’ | <assoc=right> term list | <assoc=right> term ’^’ term
17        | ’-’ term | term op=(’*’ | ’/’ | ’%’ | ’+’ | ’-’) term ;
18vals    : ’<’ ’>’ | ’<’ term (’,’ term)* ’>’ ;
19list    : ’[’ ’]’ | ’[’ item (’,’ item)* ’]’ ;
20item    : term | term ’..’ term | term ’:’ term ;
21args    : ’(’ ’)’ | ’(’ var (’,’ var)* ’)’ ;
22params  : ’<’ ’>’ | ’<’ var (’,’ var)* ’>’ ;
23nodes   : ’(’ ’)’ | ’(’ node (’,’ node)* ’)’ ;
24node    : var (io=(’?’ | ’!’ | ’:’) ID?)? ;
25var     : name list* ;
26name    : (ID ’.’)* ID ;
27NAT     : (’0’ | [1-9][0-9]*) ;
28DEC     : (’0’ | [1-9][0-9]*) ’.’ [0-9]+ ;
29BOOL    : true | false ;
30ID      : [a-zA-Z_][a-zA-Z0-9_]*;
31STRING  : ’\"’ .*? ’\"’ ;
32SPACES  : [ \t\r\n]+ -> skip ;
33SL_COMM : ’//’ .*? (’\n’|EOF) -> skip ;
34ML_COMM : ’/*’ .*? ’*/’ -> skip ;
Listing 1: Concrete ANTLR4 syntax of Treo (Treo.g4).

Consider the following Treo file ( in Table 2) representing the Alternator:

import syncdrain; import sync; import fifo1;
alternator2(a1,a2,b1) { sync(a1,b1) syncdrain(a1,a2) sync(a2,b2) fifo1(b2,b1) }

On the first line, we import () three different component definitions. On the second line, we define the alternator2 component (). Its definition () has no parameters (), and three nodes, a1, a2, and b1, in its interface (). The body () of this definition consists of a set of component instances that interact via shared nodes. The first component instance sync(a1,b1) is an instantiation () of the imported sync definition () with nodes a1 and b1 () and without any parameters ().

All nodes that occur in the body, but not in the interface, are hidden. Hiding renames a node to a fresh inaccessible name, which prevents it from being shared with other components. In the case of alternator2, node b2 is not part of the interface, and hence hidden.

Constructed from existing components, alternator2 is a composite component ). However, not every component is constructed from existing components, and we call such components primitive (). The following Treo code shows a possible (primitive) definition of the fifo1 component.

fifo1(a?,b!) { empty -{a},true-> full; full -{b},true-> empty; }

The definition of the fifo1 differs from the definition of the alternator2 in two ways.

The first difference is that the fifo1 component is (in this case) defined directly as a constraint automaton [5]. Constraint automata constitute a popular semantic sort for specification of Reo component types, and forms the basis of the Lykos compiler [13]. However, constraint automata are not the de facto standard: the literature offers more than thirty different semantic sorts for specification of Reo components [14], such as the coloring semantics and timed data stream semantics. To accommodate the generality that disparate semantics allow, Treo features user-defined semantic sorts, which means that the syntax for primitive components is user-defined. For example, this means that we may also define the fifo1 component by referring to a Java file via fifo1(a?,b!){ "MyFIFO1.java" }.

The second difference is that the nodes a and b in the interface are directed. That is, each of its interface nodes is either of type input or output, designated by the markers ? and !, respectively. In Reo, it is safe to join two channels on a shared sink node (e.g., node in Figure 1). However, the composition operators in most Reo semantics do not automatically produce the correct behavior for such nodes (e.g., see [5, Section 4.3] for further details). Therefore, most Reo semantics require well-formed compositions, wherein each node has at most one input channel end and at most one output channel end.

The restriction of well-formed compositions can be very inconvenient in practice. To ensure well-formed compositions, a programmer must implement every Reo node with more than one input or output channel end as a node component. The interface of this node component is determined by its degree, which is a pair giving the numbers of its coincident source and sink ends. Such explicit node components make component constructions verbose and hard to maintain. For convenience, the Treo compiler uses the above input/output annotations to compute the degree of each node in a composition, and subsequently inserts the correct node components in the construction. We may view the input/output annotations as syntactic sugar that ensures well-formed compositions. This feature allows programmers to remain oblivious to these annotations and well-formed composition.

The ellipses in Figure 1 signify the parametrized construction of the Alternator connector, for . This notation is informal and not supported in the graphical Reo editor [9], which offers no support for parametrized constructions. In Treo, however, we can define the Alternator connector as:

alternator<k>(a[1:k],b[1]) { sync(a[1],b[1])
    { syncdrain(a[i-1],a[i]) sync(a[i],b[i]) fifo1(b[i],b[i-1]) | i in [2..k] } }

The definition of the alternator depends on a parameter k. Since Treo is a strongly typed language with type-inferencing, there is no need to specify a type for the (integer) parameter k. The interface consists of an array of nodes a[1:k] and the single node b[1]. Here, [1:k] is an abbreviation for the list [[1..k]] that contains a single list of length k. The array a[1:2] stands for the slice [a[1],a[2]] of a, while the expression a[1..2] stands for the element a[1][2] in a (cf., Equation 2). For iteration, we write { ... | i in [2..k] } using set-comprehension ( in Table 2).

Instead of defining alternator iteratively, we may also provide a recursive definition as follows:

recursive_alternator(a[1:k],b[1],b[k]) { recursive_alternator(a[1:k-1],b[1],b[k-1])
    { syncdrain(a[k-1],a[k]) sync(a[k],b[k]) fifo1(b[k-1],b[k]) | k > 1 } }

Here, the value of k is defined by the size of a[1:k], and we use set-comprehension { ... | k > 1 } for conditional construction, as well. Indeed, the resulting set of component instances is non-empty, only if k > 1 holds. Although Treo syntax allows recursive definitions, the semantics presented in Section 5 does not yet support recursion, which we leave as future work.

We illustrate the practicality of Treo by providing code for a chess playing program [13, Figure 3.29]. In this program, two teams of chess engines compete in a game of chess. We define a chess team as the following Treo component:

import parse; /* and the other imports */
team<engine[1:n]>(inp,out) {
    for (i in [1..n]) {
        engine[i](inp,best[i]) parse(best[i],p[i])
        if (i > 1) concatenate(a[i-1],p[i],a[i]) }
    sync(best[1],a[1]) majority(a[n],b) syncdrain(b,c)
    fifo1(inp,c) move(b,d) concatenate(c,d,out) }

The for-loop for (i in [1..n]) ... and if-statement if (i > 1) ... are just syntactic sugar for set-comprehensions { ... | i in [1..n] } and { ... | i > 1 }, respectively. The team component depends on an array engine[1:n] of parameters. This array does not contain the usual data values, but consists of Treo component definitions. In the body of the team component, these definitions are instantiated via engine[i](inp,best[i]). In RSL [4, 20] and FOCAML [13], it is impossible to pass a component as a parameter, which makes these languages less expressive than Treo.

We may view the team component as an example of role-oriented programming [7]. Indeed, the team component encapsulates a list of chess engines in a component, so that they can collectively be used as a single participant in a chess match:

match() { fifo1full<"">(a,b) fifo1(c,d) team<[eng1, eng2]>(a,d) team<[eng3]>(b,c) }

Treo treats not only component definitions, but also component instances as values. By passing a single component instance as a parameter to multiple components, this feature allows component (instance) sharing (cf., [6, Figure 2]). Hence, it is straightforward to implement a chess match, wherein a single instance of a chess engine plays against itself.

4 Semantic sorts

As noted in Section 3, Reo channels can be defined in many different semantic formalisms [14], such as the constraint automaton semantics, the coloring semantics, or the timed data stream semantics. Although each sort of Reo semantics has its unique properties, each of them can be used to define a collection of composable components with parameters and nodes, which we call a semantic sort:

Definition 4.1 (Semantic sort).

A semantic sort over a set of names with values from is a tuple that consists of a set of components , a composition operator , a substitution operator , and a trivial component .

We assume that the set of names and the set of values are disjoint, i.e., . For convenience, we write for , and for . For any semantic sort , we write for its set of components, for its composition operator, for its substitution operator, and for its trivial component. The composition operator ensures that the behavior of finite non-empty compositions is well-defined. To empty compositions we assign the trivial component . The substitution operator allows us to change the interface of a component via renaming or instantiation. Let be a component and a name. For a name , the construct renames every occurrence of name in to . For a value , the construct instantiates (parameter) in to . (See Example 4.3 for an example of the distinction between renaming and instantiations.)

A semantic sort implicitly defines an interface for each component via the map defined as . If name does not ‘occur’ in , substitution of by any name does not affect , i.e., .

Example 4.1 (Systems of differential equations).

The set

of systems of ordinary differential equations with variables from

and values constitute a semantic sort. Composition is union, substitution is binding a name or value to a given name, and the trivial component is the empty system of equations. Using the semantic sort, we can define continuous systems in Treo.  

Example 4.2 (Process calculi).

Consider the process calculus CSP, proposed by Hoare [11]. The set of all such process algebraic terms comprises a semantic sort. Each process can participate in a number of events, which we can interpret as names from a given set . We model the composition of CSP processes and by means of the interface parallel operator , where is the set of event names shared by and . We define substitution as simply (1) renaming the event, if a name is substituted for an event; or (2) hiding the event, if a values is substituted for an event. Since neither STOP nor SKIP shares any event with its environment, we may use either one to denote the trivial component.  

Example 4.3 (I/O-components).

Let be a semantic sort over and . We define the I/O-component sort over using the notion of a primitive I/O-component of sort .

A primitive I/O-component of sort is a tuple , where is a component, is a set of input names, is a set of output names. For and and , define

(1)

We define substitution on primitive I/O-components as , for all and . We denote the set of primitive I/O-components over as .

An I/O-component of sort is a sequence , with , of primitive I/O-components of sort . Composition of I/O-components is concatenation of sequences. The trivial I/O-component is the empty sequence . We define substitution of composite I/O-components as , for all and . Hence, is a semantic sort.  

5 Denotational semantics

We define the denotational semantics of the Treo language over a fixed, but arbitrary, semantic sort . The main purpose of this denotational semantics is to provide a clear abstract structure that guides the implementation of Treo parsers. The syntax to which this denotational semantics applies is the abstract syntax in Table 2. The general structure of our denotational semantics is quite standard, and adheres to Schmidt’s notation [25].

Although Treo syntax allows recursive definitions, the semantics presented in this section does not support this feature. Since not all recursive definitions define finite compositions of components, extending the current semantics with recursion is not straightforward, and we leave it as future work.

Variables and terms in Treo are structured as non-rectangular arrays. The set of all (ragged) arrays over a set is the smallest set such that both and , if and for all . For example, the set of ragged arrays over integers contains all natural numbers from as ‘atomic’ arrays, as well as the array . Every ragged array has a length, which can be computed via the map defined inductively as , if , and , otherwise. If is a ragged array, we access its entries via the function application , for every . We extend the access map by defining

(2)

whenever the right-hand side is defined. Two ragged arrays and have the same structure () iff and , or and for all . We can flatten a ragged array from to a sequence over via the map defined as , if , and , otherwise.

Suppose that semantic sort is defined over a set of names and a set of values , with . For simplicity, we assume that, for every component , its support is finite. Since Treo views components as values, we assume the inclusion .

We assume that the set of names is closed under taking subscripts from . That is, if is a name and is a natural number, then we can construct a fresh name . To construct sequences of data with variable lengths, we use a map that constructs from a pair of integers a finite ordered list in .

Recall from Section 3 that a component accepts an arbitrary but finite number of parameters and nodes. Therefore, we define a component definition as a map that takes an array of parameter values from and an array of nodes from and returns a component or an error . Let be the set of all definitions. As mentioned earlier, Treo also allows definitions as values, which amounts to the inclusion .111 Such a set of values exists only if admits a pre-fixed point. In this work, we simply assume that such exists.

We evaluate every Treo construct in its scope , with finite, which assigns a value to a finite collection of locally defined names. We write for the set of scopes. For a name and a value , we have a scope defined as . For any two scopes , we have a composition such that for every we have , if , and , otherwise. The composite scope can be viewed as an extension of that includes definitions and updates from .

Let Names be the set of parse trees with root , and let be the semantics of names. We define the semantics of variables as a map where Variables is the set of parse trees with root . For a scope , we define as follows:

  1. ;

Since is closed under taking subscripts, we can define , for all and , which ensures that is always defined.

The semantics of arguments is a map where Arguments is the set of all parse trees with root . For a scope , we define as follows:

  1. ;

  2. .

Let Functions be the set of parse trees with root , and let be the semantics of functions. The semantics of terms is a map where Terms is the set of parse trees with root . For a scope , we define inductively as follows:

  1. ;

  2. , which is well-defined since ;

  3. , which is well-defined since ;

  4. ;

  5. ;

  6. ;

  7. .

The semantics of lists is a map where Lists is the set of parse trees with root . For a given scope , we define inductively as follows:

  1. ;

  2. ;

  3. .

Since we use predicates in Treo for list comprehension, we define the semantics of predicates as a map where Predicates is the set of all parse trees with root . For a scope , we define the semantics of a predicate as the set of all extensions of that satisfy . We define inductively as follows:

  1. ,

  2. If is , we define ;

  3. If is , we define ;

  4. If is , we define ;

  5. If is , we define ;

  6. If is , we define .

For set and list comprehensions, we can iterate over only a finite subset of scopes of . We ensure this by restricting the set of scopes to those solutions that are minimal with respect to inclusion of domains. Formally, we write for the set of all scopes that are minimal with respect to defined as iff , for all .

The semantics of component instances is a map where Components is the set of parse trees with root . Recall that Treo views components as values (). Given a scope , we define inductively as follows:

  1. ;

  2. , where is the semantics of primitive components;

  3. ;

  4. ;

  5. .

The semantics of component definitions is a map where Definitions is the set of all parse trees with root . For a scope , we define as follows:

  1. ;

  2. If is a component , then for an array of parameter values and an array of nodes , we define as follows: Recall from Section 3 that the number of parameters and nodes can implicitly define variables. Suppose that there exists a unique ‘index-defining’ scope such that for and . Then we have

    1. satisfies , for all ;

    2. satisfies , for all ;

    3. has no duplicates;

    4. is minimal such that properties (a)-(c) are satisfied.

    We evaluate the body of the component definition to the component , where is the composition of and . Define the map as

    Map is well-defined, because has no duplicates. Note that is finite, since we assume that is finite. We define as the simultaneous substitutions . If such ‘index-defining’ scope does not exists or is not unique, then we simply define .

We define the semantics of files as a map where Files is the set of parse trees with root . Let be the semantics of imports. For a scope , we define inductively as follows:

  1. ;

  2. .

6 Input/output nodes

As mentioned in Section 3, nodes of primitive component definitions require input/output annotations. Treo regards such port type annotations as attributes of the primitive component. For a semantic sort , we model the input nodes and output nodes of its instances via two maps satisfying , for all . If , then we call a mixed node.

Example 6.1 (Mixed nodes).

Recall the I/O component sort from Example 4.3. Let , , and be three primitive I/O components. Figure 2(a) shows a graphical representation of composition of , , and . In this figure, an arrow from a node to a component indicates that is an input node of . An arrow from a component to a node indicates that is an output node of . Node is an output node of and , and it is an input node of . Thus, is a mixed node in the composition , where is sequential composition of I/O components.  

(a)
(b)
Figure 2: Surgery on an I/O-component to remove mixed nodes.

Most semantic sorts that distinguish input and output nodes assume well-formed compositions: each shared node in a composition is an output of one component and an input of the other.

Definition 6.1 (Well-formedness).

A composition , with , is well-formed if and only if and , for all .

For well-formed compositions, the behavior of the composition naturally corresponds to the composition of Reo connectors. However, specification of complex components as well-formed compositions is quite cumbersome, because it requires explicit verbose expression of the ‘merge-replicate’ behavior of every Reo node in terms of a suitable number of binary mergers and replicators. Reo nodes abstract from such detail and yield more concise specifications. Like Reo, Treo does not impose any restriction on the nodes of constituent components in a composition. Indeed, the denotational semantics of components in Section 5 unconditionally computes the composition. To define the semantics of for a semantic sort where requires well-formedness, parsing a (non-well-formed) Treo composition needs the degree (i.e., the number of coincident input and output channel ends) of each node to correctly express the ‘merge-replicate’ semantics of that node. The degree of every node used in a definition can be known only at the end of that definition. The Treo compiler could discover the degree of every node via two-pass parsing.

Alternatively, Treo can delay applying composition in until parsing completes, Treo accomplishes this by interpreting a Treo program over the I/O-component sort , as defined in Example 4.3, wherein compositions consist of lists of primitive components. First, Treo wraps each primitive component within a primitive I/O-component . Using Section 5, Treo parses the Treo program over the semantic sort as usual, and obtains a single I/O-component .

However, the resulting composition may not be well-formed. Therefore, the Treo compiler applies some surgery on to ensure a well-formed composition. This surgery consists of splitting all shared nodes in , and reconnecting them by inserting a node component. We model these node components (over semantic sort ) as a map . For sets of names and a default name , the component has input nodes (or , if is empty) and output nodes (or , if is empty).

Definition 6.2 (Surgery).

The surgery map is defined as , where , for all , and , with and . The composition is ordered arbitrarily.

Intuitively, the surgery map takes a possibly non-well-formed composition and produces a well-formed composition by inserting node components. Although initially, multiple components may produce output at the same node. After applying the surgery map, these components offer data for the same node component via different ‘ports’.

Example 6.2 (Surgery).

Figure 2(b) shows the result of applying the surgery map to the I/O-component from Example 6.1. The surgery map consists of two parts. First, the surgery map splits every node by renaming to in , for every . Second, the surgery map inserts at every node a node component . Clearly, is a well-formed composition.  

7 Related work

The Treo syntax offers a textual representation for the graphical Reo language [2, 3]. We propose Treo as a syntax for Reo that (1) provides support for parameterization, recursion, iteration, and conditional construction; (2) implements basic design principles of Reo more closely than existing languages; and (3) reflects its declarative nature. The graphical Reo editor implemented as an Eclipse plugin [9] does not support parameterization, recursion, iteration, or conditional construction. RSL (with CARML for primitives) [4, 20] is imperative, while Reo is declarative. FOCAML [13], supports only constraint automata [5], while Treo allows arbitrary user-defined semantic sorts for expressing the behavior of Reo primitives.

Since Treo leaves the syntax for primitive subsystems (i.e., semantic sorts) as user-defined, Treo is a “meta-language” that specifies compositional construction of complex structures (using the common core language defined in this paper) out of primitives defined in its arbitrary, user-defined sub-languages. As such, Treo is not directly comparable to any existing language. We can, however, compare the component-based system composition of Treo with the system composition of an existing language.

Treo components are similar to proctype declarations in Promela, the input language for the SPIN model checker developed by Holzmann [12]. However, the focus of Promela is on imperative definitions of processes, while Treo is designed for declarative composition of processes.

SysML is a graphical language for specification of systems [10]. SysML offers 9 types of diagrams, including activity diagrams and block diagrams. Each diagram provides a different view on the same system [21]. Diagram types in SysML are comparable to semantic sorts in Treo. The main difference between the two, however, is that Treo requires a well-defined composition operator, using which it allows construction of more complex components, while diagram composition is much less prominent in SysML.

A component model is a programming paradigm based on components and their composition. Our Treo language can be viewed as one such component model with a concrete syntax. Over the past decades, many different component models have been proposed. For example, CORBA [22] is a component model that is flat in the sense that every CORBA component is viewed as a black box, i.e., it does not support composite components. Fractal [6] is an example of a component model that is hierarchical, which means a component can be a composition of subcomponents. Concrete instances of Fractal consist of libraries (API’s) for a variety of programming languages, such as Java, C, and OMG IDL [6]. Treo components and Fractal component differ with respect to interaction: Treo components interact via shared names, while Fractal component interact via explicit bindings.

8 Conclusion

We propose Treo as a textual syntax for Reo connectors that allows user-defined semantic sorts, and incorporates Reo’s predefined node behavior. These features are not present in any of the existing alternative languages for Reo. We provided an abstract syntax for Treo and its denotational semantics based on this abstract syntax. We identify three possible directions for future work.

First, since our semantics disallows recursion, a component in Treo is currently restricted to consist of a composition of finitely many subsystems. Consequently, we cannot, for instance, express the construction of a primitive with an unbounded buffer, , from a set of primitives with buffer capacity of one, . It seems, however, possible to use simulation and recursion to define in terms of : is the smallest (with respect to simulation) component that simulates and is stable under sequential composition with . These assumptions readily imply that simulates a primitive with buffer of arbitrary large capacity. Semantically, the unbounded buffer would then be defined as a least fixed point of a certain operator on components. An extension of Treo semantics that allows such fixed point definitions would provide a powerful tool to define complex ‘dynamic’ components.

Second, the current semantics in Section 5 does not support components with an identity. If we instantiate a component definition twice with the same parameters, we obtain two instances of the same component. Ideally, component instantiation should return a component instance with a fresh identity. Allowing components with identities in Treo enables programmers to design systems more realistically.

Finally, a semantic sort from Definition 4.1 consists of a single composition operator . Generally, a semantic sort consists of multiple composition operators (each with it own arity). For example, we may need both sequential composition as well as parallel composition. Extending Treo with (a variable number of) composition operators would enable users to model virtually all semantic sorts.

References

  • [1]
  • [2] Farhad Arbab (2004): Reo: a channel-based coordination model for component composition. Mathematical Structures in Computer Science 14(3), pp. 329–366, doi:10.1017/S0960129504004153.
  • [3] Farhad Arbab (2011): Puff, The Magic Protocol. In Gul Agha, Olivier Danvy & José Meseguer, editors: Formal Modeling: Actors, Open Systems, Biological Systems - Essays Dedicated to Carolyn Talcott on the Occasion of Her 70th Birthday, Lecture Notes in Computer Science 7000, Springer, pp. 169–206, doi:10.1007/978-3-642-24933-49.
  • [4] Christel Baier, Tobias Blechmann, Joachim Klein & Sascha Klüppelholz (2009): A Uniform Framework for Modeling and Verifying Components and Connectors. In John Field & Vasco Thudichum Vasconcelos, editors: Proceedings of COORDINATION 2009, Lecture Notes in Computer Science 5521, Springer, pp. 247–267, doi: