Inter-Thread Communication Tool

itf2 generates glue code for interthread communication. This program solves the following class of problems:
Each thread has a specific job. Threads generate some work for each other and send a message to the relevant thread to get it done. There is no immediate acknowledgement or builtin request identifiers. For each class of threads, there is only one instance. i.e. threads are not used for doing many instances of the same work in parallel.
The program reads a description file and generates one source file and one header file. The source file is independent of the header file, it already includes the same contents.

Notation

In this document, identifiers marked like THIS are place holders.

CCODE refers to a piece of text which is enclosed within balanced {} pairs. It can include properly terminated quotes and escape sequences can be used to encode special characters within quotes. However, the escape sequences are simply passed on, the program doesn't interpret them in any way.

Some CCODE values have a special marker in them. This marker is later replaced by some string, such as the name of a variable or struct field. The marker character is @. This character is processed whether it's inside a quote or not. When such a marker is required the associated token will be called MCODE.

FILENAME is a sequence of non-whitespace characters. No quotes are necessary and are included as part of the file name if present.

Input File

An input file consists of directives and thread descriptions. Directives tell the program about how to generate the code. Here is the list of directives:
header CCODE
Inserts the given code before the generated code, but after the library code. This is useful for including local headers to get your types declared.
footer CCODE
Appends the given code after everything else.
header_file FILENAME
Header code is put into the given file. If header_file is not given, then no header is generated.
source_file FILENAME
Code is put into the given file. If source_file is not given, then no source code is generated.
system_headers CCODE
The given code replaces the system headers section in the code. This might be useful for adapting the generated code to your system.
Type declarations shall be used to introduce necessary C types to the system. Here is the syntax for it:
  type NAME MCODE1 MCODE2

     or

  type NAME MCODE1 dontfree
The name is a simple C identifier. MCODE1 tells the program how to declare a variable or struct field of the given type. @ is used to mark the place where the identifier should go. Here are some examples:
  {unsigned char @}
  {void (*@)(int,int)}
  {int @[]}
Note that the program includes only one C type by default: int. MCODE2 is used for destroying a variable of the given type. The marker is used for marking the places where the relevant expression should be put. When you send a message to a thread, that thread executes the relevant function. After that, the generated code destroys the arguments. If you give dontfree as the destruction code, then the respective argument doesn't get deallocated. Otherwise code in MCODE2 is used. Here are some complete type declarations:
  type tempstr {char *@} {free(@};}
  type byte {unsigned char @} dontfree
  type node {struct node *@} {free(@->data); free(@->key);}
Threads consist of function declarations and thread properties. The top level declaration of a thread is as follows:
  thread NAME
  {
    properties-and-functions
  }
There is only one property currently, and it's an optional one:
  free_running FUNCTION_NAME
The rest of the thread definitions should consist of function declarations:
  function NAME(TYPE1 ARG1, TYPE2 ARG2, ... TYPEn ARGn) 
TYPEi is a type name declared using the type directive. ARGi is a simple C identifier. A function can have any number of arguments, including zero.

The Command Line

The only command line argument is the name of the input file. If it's - then stdin is used.

Generated Code

The generated code contains functions which prepare a message and send it to the appropriate thread. Here is an example:
 thread worker
 {
  function process(int a)
 }
One function and two declarations will be generated:
   // implement this function in your code
   // it will be executed from the worker thread
  void worker_process(int a);

   // this will be implemented by the generated
   // code. you call this function from another
   // thread to cause the worker thread to call
   // the above function.
  void worker_PROCESS(int a);
It's that simple. You can also declare a thread to be free_running. Normally, a thread (other than the main thread) spends its time inside an event loop, waiting for messages. When a message comes in, it processes that message and then comes back for more.

A free running thread spends its time running the given function over and over again. Between these calls, it checks for a message from other threads and processes those if there is any. The function given to the free_running directive must have the prototype:

  void name();

Free running threads have two states: paused and running. Initially, they start out in paused state and they must be resumed using the following function:

  void itf_resume_threadname();
If you want to pause it from the main thread, or if you run out of work to do in the free running function, you can pause the thread using:
  void itf_pause_threadname();

Initialization

At the beginning of your program, you should initialize the main thread structs and the other threads using the following functions:
  int itf_init_main();
  void itf_init_threadname();
The init_main() function returns a file descriptor. I'll describe its use in the next section.

When you're done with a thread, you can send a quit message to it. This also joins the thread, making sure that it's really dead before returning. Repeatedly quit()ing and init()ing a thread hasn't been tested. It may or may not work. This quit operation is done by:

  void itf_quit_threadname();
This probably should be done from the main thread of the program since it also tries to join with the relevant thread and I have no idea whether it'd work from another.

The Main Thread

You can also have messages routed to the main thread. The difference between the main thread and the others is that the main thread follows the normal flow of your program while the others either go into an event loop or execute the free_running function. In order to receive messages and execute the corresponding functions from the main thread, you can use the following:
    // this function returns non-zero if there are pending messages
    // for the main thread
  int itf_pending_main();

    // reads one message from the queue and executes it.
  void itf_process_main();
The syntax for the main thread is the same as the other threads. The recognized name for it is main.

If you already use select() to monitor file descriptors in the main thread, you can use the file descriptor returned by itf_init_main() to see whether there is a pending message for main. You must not read from this file descriptor. itf_process_main() will stall if you do so.

Download and Install

Here is the latest source package. Simply running make should do it. If you don't have my t2c tool, you should run
  touch *.cx && make
You can then install the binary itf2 anywhere in your $PATH.

Things to Do