In this essay, I discuss the design of a box-and-arrow diagram language (PSL)1 that describes a system of concurrent, isolated software components.

Software components are drawn as boxes and event-flows2 between the components are drawn as arrows.

I treat the diagrams as a syntax for a PSL.

The diagram language is based on a set of conventions. N.B. programming languages are, also, based on conventions - you can’t get them to compile just any English phrase. The language manual tells you what phrases are acceptable. Similarly, our diagram language manual (this essay) tells you what phrase are acceptable.

The language in this essay uses ASCII + boxes + lines. And, it allows these gratoms to overlap3


The goal of this language (PSL)4 is to describe software components and events flowing between them.


Isolation means:




Leaf Components

Composite Components

Input Event Queues

Output Event Queues

Input Pins

Output Pins

A component can have 0 or more output “pins” - input ports tagged with a name (symbol, index, etc.).


Input Event Delivery

Output Event Delivery

Dropping (Skipping) Events

Event Delivery Guarantees

The system guarantees only that an output event sent to multiple receivers is delivered “at the same time” to all receivers.

This implies that the dispatcher() follows the following protocol for delivering an output event:

Obviously, the lock-time determines the throughput of the system. This is a matter for Optimization Engineering. It is probably problem-specific. One solution does not fit all.

Digital hardware works this way. A system that is still locked when new inputs arrive is said to “over-run”. It is, also, deemed to be “too slow” to solve a particular problem. The definition of slow-ness depends on the problem and cannot be easily generalized. Production Engineering7 can worry about whether a penny can be saved by using slower components and avoiding over-kill (over-design) for a particular problem.

Top Level Component

Bottom Level Component

Mid-Level Component

Namespaces - Name Clashes

Long-Running Loops

Data in Events

Example: UNIX® pipelines. Events in a pipeline are lines of characters terminated by ‘\n’ (0x0a). End of file is 0x04. No other structuring is implied. Some programs, e.g. awk, assume and create fields within the lines of characters. UNIX® does not control this interpretation of the data, it simply delivers events as whole lines (or as EOF).

Example: network protocols, e.g. OSI 7-layer. Each layer treats packets of bytes. The bottom-most layers key on certain bytes at the beginning and end of packets, but don’t impose further structure on the contents of the packets.

Example: FBP9 sends IPs through the system. The FBP system recognizes bracketed IPs, but imposes no further structure on the data contained in the IPs.

Dispatching / Routing Events


Control Events

In general, a component Send()s control events to its children.

Data Events

In general, a component Send()s data events to its parent (for further routing by the parent).

Trigger Events

Some events are sent only for their effect - the data value doesn’t matter, only the fact that the event arrived matters.

I call these triggers.14

Triggers are even “smaller” than Booleans. Triggers don’t have a value.

  1. Problem–Specific Language  ↩

  2. aka data flows  ↩

  3. Whereas most textual languages don't allow characters to overlap.  ↩

  4. Problem Specific Language – a kind of DSL (Domain Specific Language)  ↩

  5. Question: if the data is non–scalar, is it copied (by value) or is only it pointer copied (by reference)? I don't know. The answer probably depends on the problem space. One solution does not fit all.  ↩

  6. Garbage Collect  ↩

  7. See my essay “Roles for Software Engineering”.  ↩

  8. side–effects are OK when components are fully isolated  ↩

  9. Flow–based Programming.  ↩

  10. self–modifying code is not allowed ; see digital electronics circuits' sockets for alternate ways to view dynamic construction  ↩

  11. Children components can only “see” their own output ports, and cannot name the receiving components directly. Children Send() outputs to their own output ports. The dispatcher() distributes output events (later), according to the routing table contained in the parent component.  ↩

  12. Unbounded recursion is a bug.  ↩

  13. N.B. for preemption–based systems (e.g Linux, Windows, MacOSX, etc.), this worst–case analysis is so bad, that the numbers are not published and/or ignored, in general. The analysis is, also, overly–complicated for such systems.  ↩

  14. Influenced by digital hardware design.  ↩