Introduction ============ This is a quick introduction to generic components and interfaces, the new features of nesC 1.2. A generic component is a component that can be multiply instantiated at compile-time and which takes an optional parameter list (including type arguments). A generic interface is an interface with type arguments. This allows, e.g., the creation of reusable adapter components (such as "converting" a StdControl to a SplitControl interface), simplifies implementations of components with much in common (such as the TinyDB attributes), etc. Generic Components ================== Both generic configurations and generic modules are supported. The syntax is straightforward, as this generic queue of ints (with no empty/full detection) shows: interface IntQueue { command void push(int x); command int pop(); } generic module IntQueueC(int n) { provides interface IntQueue; } implementation { int q[n]; int p1 = 0, p2 = 0; command void IntQueue.push(int x) { q[p1++] = x; if (p1 == n) p1 = 0; } command int IntQueue.pop() { int ret = q[p2++]; if (p2 == n) p2 = 0; return ret; } } The n argument to IntQueueC can be used within the component as a constant expression; in this example it is used to dimension the q array. Generic components are instantiated in the `components' declaration of configurations: configuration MyApp { } implementation { components MyCode, new IntQueueC(10) as MyQueue; MyCode.Queue -> MyQueue; } This creates a new 10 element int-queue, accessible as MyQueue within the MyApp configuration. This new component is not accessible outside MyApp, except via any interfaces wired within MyApp. You can also have generic configurations: generic configuration SomeAssembly() { provides interface X; } implementation { components new SomeAssemblyM(), new IntQueueC(10) as MyQueue; X = SomeAssemblyM.X; SomeAssemblyM.Queue -> MyQueue; } Every time SomeAssembly is instantiated, new instances of SomeAssemblyM and IntQueueC will also be instantiated. unique, uniqueCount work "correctly" (i.e., usefully ;-)): if unique is used within a generic component C, it returns a different value in each instance of C, and the call within the definition of C itself does not affect the value returned by uniqueCount. Instantiating a generic component is effectively equivalent to creating a new component with the parameters replaced by the argument values. Thus, instantiating a generic module will produce two slightly different versions of the module's code; beware of code size explosion if you instantiate many modules... A future version of nesC may support code sharing between instances of generic modules, with some restrictions and at some runtime cost. Generic Interfaces and Type Parameters ====================================== Finally, generic components can also have type arguments. This is most useful in conjunction with generic interfaces, e.g., to create a queue of any type: interface Queue { command void push(t x); command t pop(); } generic module QueueC(typedef t, int n) { provides interface Queue; // Note: this is a shortcut for // provides interface Queue as Queue // hence the declarations below refer to Queue, not Queue. // Stylistically it's probably best to give a name to the // provided or used interface. } implementation { t q[n]; int p1 = 0, p2 = 0; command void Queue.push(t x) { q[p1++] = x; if (p1 == n) p1 = 0; } command t Queue.pop() { t ret = q[p2++]; if (p2 == n) p2 = 0; return ret; } } The Queue interface takes a single type parameter t, which can be used within Queue as if it were a typedefed type. Type arguments must be provided for generic interfaces in uses and provides declarations. The generic component queue takes a type parameter t (using the funky "typedef t" syntax), and a size as in the IntQueue example. As with generic interfaces, component type parameters can be used as if they were typedefed types. Generic components with type arguments are instantiated just like other generic components: configuration MyApp { } implementation { components MyCode, new QueueC(int, 10) as MyQueue; MyCode.Queue -> MyQueue; } One restriction: type arguments to generic components and interfaces can not be of array, function or incomplete type. By default, the only operations that can be performed on a value of a type parameter are copying it, and passing it as an argument to some function. If a generic component needs some functionality on its type parameter, it can express that need by using an appropriate interface: interface Compare { command bool lessthan(t x1, t x2); } generic module Sort(typedef t) { uses interface Compare; } implementation { ... void f() { t x1, x2; ... if (call Compare.lessthan(x1, x2)) ... } It is also possible to declare, using the special @integer() and @number() attributes, that a type parameter is of an integral (respectively numerical) type. In that case, all the operations allowed on integers (respectively, integers and floating-point numbers) are allowed: generic module Sort(typedef t @number()) { } implementation { ... void f() { t x1, x2; ... if (x1 < x2) ... } Of course, the type argument to Sort must then be an integral or floating-point type. Type and constants in Component Specifications and Other Changes ================================================================ As part of the changes for generic components, nesC now allows types and enum constants to be defined in the specification of a component (both in generic and non-generic components). These types can be used in the component in which they are defined, and in configurations that include the component (as arguments to generic components or to define other types and constants). For example, interface Send { command result_t send(t x); event result_t sendDone(); } generic configuration RadioStack(typedef messageType) { typedef messageType *messagePtr; provides interface Send; } implementation { ... } configuration MyRadioStack { provides interface Send; } implementation { components new RadioStack(MyMessageType) as TheActualStack; Send = TheActualStack.Send; // Build a queue of the message pointer type components new QueueOf(TheActualStack.messagePtr) as MyQueue; ... } This example also shows that you can now intersperse wiring statements and component declarations. Additionally, you can have typedefs in the body of a configuration, so MyRadioStack could also be written as: configuration MyRadioStack { provides interface Send; } implementation { components new RadioStack(MyMessageType) as TheActualStack; Send = TheActualStack.Send; // Build a queue of the message pointer type, using a local typedef typedef TheActualStack.messagePtr MyMessageTypePtr; components new QueueOf(MyMessageTypePtr) as MyQueue; ... } Note that you can refer to typedefs and enum constants in other components, but not to struct, union or enum tags, i.e., module Fun { struct silly { int x; }; } implementation { ... } configuration Barf { } implementation { components Fun; typedef struct Fun.silly baz; } is not allowed, but module Legalfun { typedef enum { ONE = 2 } sillier; } implementation { ... } configuration Ok { } implementation { components Legalfun; typedef Legalfun.sillier baz; enum { THREE = Legalfun.ONE }; } is.