Table of Contents

Fcl: The FAM Command Language

Fcl: The FAM Command Language

1 Introduction

FCL (pronounced "fickle") is an extension of the TCL language designed to support the construction of environments to support the design of software at the architectural level. FCL implements an object system with the following features:

The system is implemented in a combination of TCL and C. A C library defines a TCL interface to the Exodus Storage Manager client library. This library provides persistence, client/server operations, transactions and recovery. A small TCL library implements the object system.

The implementation is designed to be lightweight and easy to modify and extend. This design goal is important because it will allow us to easily experiment with different notions of architectural style, and different object representations to support style. The rest of this document will describe the high level interface to the object system, the current architectural objects library, and the implementation of the C library.

2 Basics

2.1 Classes

In the current implementation, new styles are implemented by defining subclasses of one or more of the basic architectural classes. These subclasses will both constrain the behavior of built-in methods and define new interfaces that are specific to a particular style. For example, an RPC style might define new port classes for the client and server sides of an RPC, and also new methods on these ports for generating stub code, or checking for consistent interfaces.

FCL provides three new primitive commands for defining new classes and methods:

class <new_class> [super_class]
Define a new class called <new_class>. If present, super_class specifies that
new_class should inherit all the slots and methods of super_class.

slot <class> <name> [initial]
Defines a new slot called name for class. If present, initial specifies an initial value for the slot. Slots are manipulated using the getslot and setslot commands. Examples of their use will be shown in a bit.
If class has a superclass and a slot named name exists in the superclass, then the new definition overrides the previous one.
method <class> <name> <args> <body>
Defines a new method called name for class. Within the body of the class, the variable $self refers to the object that dispatched the method call. For example:
class int 
slot int value 0
method int plus {x} {
  set currentval [$self getslot value]
  set currentval [ expr $currentval + $x ]
  $self setslot value $currentval
}
Defines a class called int with one slot called value. The method plus retrieves value from the implicit parameter self and replaces it with the sum of value and the parameter x.
If class has a superclass and a method named name exists for the superclass, then the new definition overrides the old one.

2.2 Objects and Transactions

By default, all FCL objects are allocated in the persistent store when they are created. Therefore, all operations on FCL objects must be performed within a transaction block. The syntax for this is simple:

trans { <statements > }
This command opens a transaction in the storage manager and executes all the commands within the block in the context of this transaction. Nested calls to trans are allowed, but are simply treated as noops. Within a transaction, the abort command will abort any currently running transaction all the way back to the top-level trans block. The commit command is either a noop, or commits the top level transaction.

To create a FCL object, you invoke its class name as a command within a transaction:

class foo 
slot foo fooslot1 1
slot foo fooslot2 2
# now make a new foo
trans { foo newfoo }
=> _OBJ(12345678)
The string _OBJ<X> is the unique name of the new object in the persistent store, and is also the name of a new command that refers to the object. The FCL library sets the value of the variable newfoo to this string, so to manipulate the new object, we say:

trans { $newfoo getslot fooslot1 }
=> 1
trans { _OBJ(12345678) getslot fooslot2 }
=> 2
trans { $newfoo setslot fooslot1 10 }
trans { $newfoo getslot fooslot1 }
=> 10
This name serves as a unique pointer to the object and can be used as such. At this point, persistent objects are not garbage collected. Later on, we'll add some support for this in large designs.

Note: Each new object command actually refers to a generic dispatch procedure which is implemented partly in C and partly in tcl. The C part handles all object methods that interact with the Exodus library at a low level, and the loop that looks up the appropriate method to call based on an object's class. The tcl part handles subclassing, high level method dispatch and so on. In this way, the high level behavior of the object system can be changed without having to modify the low level Exodus interface.

2.3 Delegation to a Superclass

By default, methods are dispatched according to the class of the object command that is invoked to call them. That is, when you say "$object methodname", FCL uses the class of $object to lookup to method named methodname. You can override this behavior using the as construct. To dispatch the method according to some superclass of an object's class, you say:

trans { $object as <superclass> <methodname> }
The superclass must be an actual superclass of the object's class, otherwise an error is returned.

2.4 Runtime Type Information

You can use the builtin method myClass to retrieve the name of the class that an object belongs to. In addition, you can use the builtin method isa to check to see if an object belongs to a particular class or any of its superclasses.

2.5 Initialization and Related Matters

The FCL system automatically initializes itself with the new commands for the class system and opens a connection to the Exodus server. You just have to set up a few environment variables correctly. In csh, do this:

% source /usr/local/bin/ablesetup.csh
% /usr/local/bin/fcl
The FCL initialization procedure just sources fam_base.fcl out of /usr/local/lib/fcl, which in turn sources fobj.tcl. It also calls an internal command, famInit to open a server connection. While it is safe to call famInit again and again, you don't have to worry about it.

2.6 Creating and Retrieving Files

The Exodus storage manager does not provide a filesystem like interface to its data. So, if we create and object, we have to be sure to store its ID in a well defined place or we'll never be able to get it back again.

The FCL library provides a simple, single level directory structure for binding names to object IDs. In FCL, a file is all those objects accessible from a single root object through object pointers contained in the root object or objects pointed to by the root object:

To create a new file in FCL, you just create the root object and bind it to a name:

% trans { CSomeClass newRoot }
% trans { $newRoot setvalue "hello" }
% famNew aFileName $newRoot
This stores the binding between the object handle newRoot and the name aFileName in a toplevel table maintained by FCL. You can delete a binding using the famDel procedure. You say:

famDel afileName.
The famOpen procedure retrieves a root object from the binding table:

% set myRoot [ trans { famOpen aFileName } ]
% trans { $myRoot getvalue }
=> hello
You can also find all the files in the database that match a given name by using the famMatch procedure:

% famMatch des-*
=> all files that begin with the string des-
Finally, the current implementations of the UI and the Shelf use the prefixes des- and shelf- to identify their files. In addition, the UI creates at least two files for each design, one for the design and one for the visualization of the design. The visual file is called des-<name>, while the design file is called des-<name>.db.

Note: Right now, the FCL directory table is just a TCL list, which means its just a big string. Clearly, in order for the system to scale to real world proportions, we need to have a more efficient structure for this purpose. Later on, we'll probably replace this with Exodus index files, and also add facilities for allowing trees of directories.
Normally, these root object are top level aggregates in a design, and will be created and managed by the UI.

2.7 Tables

Tables are a Tcl interface to Exodus index files. Exodus index files provide a persistent hash table or B-tree structure that holds an assosiation between string keys and objects in the database. In the B-tree stucuture, keys can be accessed in sorted order, while in the hash table no ordering is imposed on the keys. The following commands are related to creating tables:

table <name>
Make a new table in the variable called <name>.
<table> add key value
Add a entry in the table <table> with a given key and value. Values should be either object or table handles. Fcl checks this, Fish does not.
<table> find key
Find the value associated with the key <key>.
<table> replace <key> <newvalue>
Replace the value associated with <key>. Signals an error if <key> does not exist in the table.
<table> delete <key>
Remove the table entry for <key>.
<table> startsearch 
Start a scan of the table <table>. Returns a search ID that you use in further manipulations of this scan. This is just like scanning Tcl arrays.
<table> nextelement <sid>
Bump the given table scan ahead one element. Returns -1 when there are no more elements to scan.
<table> donesearch <sid>
Throws away the data structures associated with the search <sid>.
<table> names
Returns a list of all keys defined in the table.
Note: Tables aren't used a lot in the current prototype. The shelf will be using them for queries, and the current Database and UI could use them instead of list structures for file namespace managment and perhaps representing aggregates.

2.8 Events

Finally, the FCL interpreter has a ToolTalk based extension for inter-application communication. The following commands are exported by this extension:

You do one of the following events from a Fcl with the event system compiled into it:

event register <event name> <command>
Tells the event system you want to receive events named <event name>, and to execute the command whenever you receive them. For example:
event register startup {eval exec $eventArgs &}
would cause you to execute whatever was passed to you in the event's arguments whenever you receive an event named `startup'.
event unregister <event name>
Stops listening for events with the given name
event broadcast <event name> [event arguments]
Sends an event to all processes registered for that event. For example:
event broadcast startup "gnu-emacs /etc/passwd"
would broadcast the event named "startup" with the argument "emacs /etc/passwd". If a process had executed the command listed in section1 to register for the startup event, this would cause gnu-emacs to display the file /etc/passwd in the background.
event info [event name]
Without any arguments, this command displays all the events that you are registered for. With one argument, it displays the command that will be executed when you receive the named event. With two arguments, it displays a usage message.

3 Architectural Design Classes

The top level interface to FCL is a set of classes whose instances represent the primitive architectural design objects. These are listed below:

CFamObject Root of inheritance tree.

CFamComponent A generic component.

CFamConnector A generic connector.

CFamPort A generic port.

CFamRole A generic role.

CFamRep Abstract class for representations. Never instantiated.
CFamExternalRep A pointer to a representation for a design object that is not stored in the database.

CFamAggregate A representation of a design object in terms of other, more primitive design objects. Aggregates use two other primitive classes called CFamBinding and CFamAttachment internally.

Note: All objects have a copy method that does a deep copy of the object. That is, it copies all of the object and it copies everything that is logically a child of that object including representations, and if appropriate bindings and attachments.
In addition, each slot usually has associated with it access and assignment methods. If these methods are meant to be public, they are mentioned here. Generally, the assignment methods are meant only for internal use and not documented here.

3.1 CFamObject

The class CFamObject contains three slots and two methods:

slot CFamObject sName noname
Contains the name of the object. Used to be that names were unique in an aggregate. The current implementation does not enforce this as yet.
slot CFamObject sParent nil
The logical parent the object in a design. Used when objects are contained within other objects, like ports within a component.
slot CFamObject sAttrs
An scratchpad list of attributes and values. Tools can leave meta-data here, but need to be able to make sense of it on their own.
slot CFamObject sViews
A list of other objects that "view" this one. This is inteneded for use by the UI for visualizations of designs, but could be used by other tools that need to monitor objects and trigger code when they are modified.
The following methods do data access or copying.

method CFamObject getName
method CFamObject setName
method CFamObject getParent 
method CFamObject getViews
method CFamObject getAttrs
method CFamObject deepCopy
These two methods add and remove viewer objects from the views list of an object:

method CFamObject addView
method CFamObject delView
Other methods defined on this class are logically private and should not be used in user level code.

3.2 Ports and Roles

The classes CFamPort and CFamRole represent ports and roles.

slot CFamPort sAttachment nil
Holds the roles that this port is attached to. See Section 3.4 below.
slot CFamPort sBinding nil
Holds the ports that this port is bound to in an aggregate. See Section 3.4 below.
method CFamPort getAttachments 
Returns the list of role that this port is attached to.
method CFamPort getBindings
Returns the list of bindings involving this port.
In addition, ports have the following private methods. These methods should only be used by the aggregate that owns the parent of the port or role.

method CFamPort __addAttachment {r}
Add a role to the attachments list of the self.
method CFamPort __rmAttachment {r}
Remove the attachment to the role r from the attachments list of self.
method CFamPort __addBinding {p}
Add a port to the bindings list of self.
method CFamPort __rmBinding {p}
Remove the binding to the port p from the bindings list of self.
The class CFamRole is symmetrical to CFamPort, and has the same interface.

3.3 Components and Connectors

slot CFamComponent sPorts {}
A list of ports that this component owns.
slot CFamComponent sReps {}
A list of representations for this port. A representation is any number of things including an aggregate of other components and connectors, a pointer to external data (such as code, or a formal spec.), and so on.
method CFamComponent getPorts
Return the port list
method CFamComponent getReps
Return the list of representations.
method CFamComponent addPort {p} 
Add a port to the port list. Does a type check. You can't add the same port twice.
method CFamComponent delPort {p} 
Remove a port from the port list.
method CFamComponent addRep {r} 
Add a representation to the rep list.
method CFamComponent delRep {r} {
Remove a representation from the rep. list.
The class CFamConnector has the same interface to CFamComponent, but with roles replacing ports everywhere.

3.4 Aggregates

Aggregates store collections of primitive design objects and encapsulate them within one logical object. Bindings define the relationship between the ports and/or roles of the logical object with ports and roles of one or more of the primitives. For example, if the logical object were representing a component, then one would define bindings between some set of child components and the ports of the component that the aggregate is representing:

slot CFamAggregate sChildren {}
This is a list of the objects that the aggregate contains.
slot CFamAggregate sBindings {}
This is a list of the bindings that the aggregate contains.
slot CFamAggregate sAttachments {}
This is a list of the attachments that the aggregate contains.
method CFamAggregate getChildren 
Return the list of children for this aggregate
method CFamAggregate getBindings
Return the list of bindings for this aggregate
method CFamAggregate getAttachments
Return the list of attachments for this aggregate.
method CFamAggregate addChild {c} 
Adds a child to the aggregate.
method CFamAggregate delChild {c} 
Removes a child from the aggregate.
method CFamAggregate bind { p1 p2} 
Binds the objects p1 and p2. These can be either two ports or two roles. This operation does all the necessary semantic checks, creates the binding object, and adds it to the binding list. You should generally use this rather than the low level calls.
method CFamAggregate attach { p1 p2} 
Attaches the objects p1 and p2. One must be a port and the other must be a role. The aggregate does any necessary semantic checks and sets the sAttachment slots of the two objects. Later on, the aggregate may keep a list of attachments, much like its current list of bindings. But we're not sure about that yet.
method CFamAggregate unbind { p1 p2} 
Removes any bindings in self between p1 and p2.
method CFamAggregate detach { p1 p2} 
Removes any attachments in self between p1 and p2.
The binding and attachments code uses two internal data structures and several internal methods on these data structures.

First, the two classes CFamBinding and CFamAttachment are used to represent bindings and attachments. The class CFamBinding is defined as follows:

slot CFamBinding sInner
Holds a pointer to the inner port/role of a binding. Don't mess with this directly, use the bind method in CFamAggregate.
slot CFamBinding sOuter
Holds a pointer to the outer port/role of a binding. Don't mess with this directly, use the bind method in CFamAggregate.
The internal methods __setInner, __getInner, __setOuter and __getOuter provide access to the slots from an aggregate. They should not be used by anything other than an aggregate. The aggregate is responsible for maintaining all appropriate invariants on the bindings that it owns.

The class CFamAttachment is similar:

slot CFamAttachment sPort
The port in this attachment.
slot CFamAttachment sRole
The role in this attachment.
As before, the internal routines __getPort, __setPort, __getRole and __setRole provide data access to the aggregate that owns the attachment.

Internally, the aggregate uses the four routines, __addBinding, __delBinding, __addAttachment and __delAttachment to manipulate bindings and attachments. These are called from the public routines bind, unbind, attach and detach, and should not be used directly by anyone. Subclasses of CFamAggregate only need to override the public methods to do extra checks before delegating to their superclass.

3.5 External Reps

External representations specify a representation for a design objects that lives outside the database, in the file system for example.

slot CFamExternalRep sExtData 
Holds the name of the external object representation. Usually a file name.
method CFamExternalRep getData
Returns the name of the external object.
method CFamExternalRep setData { newData }
Stores a new external name in the external representation object.

Peter Su