SDEditGen is a preprocessor tool for Quick Sequence Diagram Editor, better known as sdedit. QSDE / sdedit is an excellent open source text to UML tool, but its syntax is not so nice: it is often unintutitive, cryptic, and hard to remember. Also, like syntax of many sequence diagram tools, sdedit’s syntax is unstructured. It is easy to create and read a Hello world (Bob → Alice) example, but it is much harder to write and read complex real-world diagrams. SDEditGen aims to provide a structured, nicer, easier and more intuitive syntax for sdedit.

QSDE / sdedit: good parts

Nice desktop GUI application

Quick Sequence Diagram Editor is a GUI application, with a tree view containing open diagrams on the left, a text editor on the bottom right and a real-time diagram preview pane on the top right:

BreadthFirstSearch

Plenty of features

QSDE / sddit offers some features which are not very often found in sequence diagram tools, like real time diagram server and multithreading support, as well as not so exotic but nice features like automatic redrawing.

In my view this is the most important feature for real-world complex diagrams! For toy examples it is not needed, of course.

Links

The diagram to the right includes the links to diagrams Create_Matadata.sd and Create_leaf.sd. When the user clicks on a diagram link the application will replace the current diagram with the clicked one, and also display the newly opened diagram name in the tree view.

Why is this feature so important? The main reason why we need UML diagrams at all is software complexity. Therefore the diagrams worth to be drawn are also complex, and this means they tend to become big pretty soon. In my view a single diagram should not become too big. Perhaps a meaningful limit for a single diagram is that it horizontally fits a screen width in the full-screen mode and that vertically it is not much bigger than what fits in the full screen. If we want to follow this tip the only solution is to break a potentially big diagram into smaller interlinked diagrams.

There are many text to UML tools but it is questionable whether any open source or inexpensive tool besides sdedit includes a similar functionality.

What’s wrong with sdedit

Unstructured syntax

An important use case for sequence diagrams is when we need to understand an unfamiliar, complex and undocumented source code.

The source code written in modern programming languages is structured: individual declarations and instructions reside in classes and methods, they are not just floating around.

On the other hand, the code in sdedit and many other text to UML tools is unstructured: these tools don’t provide the means to reflect the structure present in the source code.

The basic syntax for sending messages in sdedit is following:

<caller>:<callee>.message

These instructions are by far the most common ones, so our program is mostly just a sequence of such instructions.

Do repeat yourself

Object-oriented programming languages don’t force us to specify the current object when we call methods of other objects. Unlike that, when we are writing sdedit code we must specify the current object each time. For example:

manager:protocolBuilder.new
*1 manager
    link:Create_Metadata.sd
*1
manager:protocolBuilder.add(Metadata)
*2 manager
    link:Create_Leaf.sd
*2
manager:protocolBuilder.add(Leaf)
DiagramLinks

This affects readability badly.

Cryptic syntax

Let’s illustrate the problematic syntax on just one example.

In the following line it looks that the source object bfs assigns the variable adjList, but actually this specifies that the target object’s method getAdjacentNodes returns adjList, which is something quite different:

bfs:adjList=node.getAdjacentNodes()

SDEditGen: structured syntax for sdedit

Object creation & method calls

The most important operations supported by SDEditGen are object creation and method calling.

QSDE / sdedit models both operations as message passing:

sourceObject:targetObject.new(arguments)
sourceObject:targetObject.method(arguments)

The SDEditGen’s basic idea is to avoid specifying the source object in every object creation and method call: the source object should be determined implicitly. The SDEditGen’s syntax for these operations is simply:

new targetObject(arguments)
call targetObject.method(arguments)

How can we achieve the source object to be implicitly known? There are several ways, but let’s show the simplest way first.

Object as context

Let’s say the object test creates the object adapter and we don’t care in which particular method this is happening. We can specify it like this:

object test {
    new adapter
}
NewAdapter
The object statement implicitly provides the context (the source object) for the enclosed operations.

If the object test calls a method on the created object, we also don’t have to specify the source object because it is known from the context:

object test { (1)
    new adapter
    call adapter.init
}
1 The object test is the source object for both enclosed operations.
NewInit

Method call as context

So far so good, but how can we specify the context if adapter’s init method creates another object?

We can do it easily because adapter’s init method call itself can serve as a context:

object test { (1)
    new adapter
    call adapter.init { (2)
        new manager
    }
}
1 The object test is the source object for both enclosed operations (new and call).
2 The object adapter is the source object for the enclosed operation (new).
NewInitManager
The method call statement implicitly provides the context (the source object) for the enclosed operations.

Object creation as context

What if the manager’s constructor creates yet another object?

You guess it: the manager’s creation instruction can also implicitly determine the source object for instructions (object creations and method calls) performed in the constructor:

object test { (1)
    new adapter
    call adapter.init { (2)
        new manager { (3)
            new context
        }
    }
}
1 The object test is the source object for both enclosed operations (new and call).
2 The object adapter is the source object for the enclosed operation (new).
3 The object manager is the source object for the enclosed operation (new).
NewInitManagerContext
The object creation statement implicitly provides the context (the source object) for the enclosed operations.

Object declarations

Finally we are ready to show the complete example:

objects { (1)
    test: AdapterTest | named existing (2)
    adapter: Adapter
    manager: Manager
    context: Context
}

object test {
    new adapter
    call adapter.init {
        new manager {
            new context
        }
    }
    call manager.manage(arg1, arg2) {
        loop {
            call context.getItem
        }
    }
}
1 The keyword objects encloses the object declarations.
2 The words named and existing are flags describing object attributes. They corresponding to sdedit flags.
SimpleDiagram

Return clauses

Adding returned objects or values to diagrams can significantly improve readability. SDEditGen’s syntax supports return clauses in object creation and method call statements:

new targetObject(arguments) return expression
call targetObject.method(arguments) return expression

In my view this syntax is considerably easier to read and understand than the original sdedit syntax. The following diagram includes several return clauses:

object bfs {
    new queue
    call someNode.setLevel(0)
    call queue.insert(someNode)
    loop "while queue != ()" {
        call queue.remove() return node (1)
        call node.getLevel() return level
        call node.getAdjacentNodes() return adjList
        loop "0 <= i < #adjList" {
            call adjList.get(i) return adj
            call adj.getLevel() return nodeLevel
            alt "nodeLevel IS NOT defined" { (2)
                call adj.setLevel(`level+1`)
                call queue.insert(adj)
            section "else"
            }
        }
    }
    call queue.destroy()
}
1 An example of a method returning an object
2 The alt construct corresponds to [c:alt] construct in sdedit
BreadthFirstWhole

The following diagram contains several diagramLink statements:

objects {
    test: ProtocolAdapterTest | existing
    adapter: ProtocolAdapter
    configurator: ProtocolAdapterConfigurator
    confHelp: ConfigurationHelper
    manager: Manager
    protocolBuilder: ProtocolBuilder
    protocol: Protocol
}

object test {
    new adapter;
    call adapter.init(configuration) {
        new configurator(this, configuration) {
            new confHelp(this);
        }
        new manager(configurator) {
            new protocolBuilder;

            diagramLink "Create_Metadata.sd" 1; (1)
            call protocolBuilder.add(Metadata);

            diagramLink "Create_Leaf.sd" 2;
            call protocolBuilder.add(Leaf);

            loop { (2)
                call protocolBuilder.add(Hierarchy);
            }
            call protocolBuilder.add(Top);
            call protocolBuilder.add(Timestamp);
            call protocolBuilder.build {
                new protocol(this) {
                    call protocol "Copy items from builder"; (3)
                }
            }
        }
    }
}
1 The diagramLink statement creates a link to another sdedit diagram
2 The loop construct corresponds to [c:loop] construct in sdedit.
3 A method name doesn’t have to be an identifier, it can be descriptive. The dot character between object name and method name is optional. A statement can end with a semicolon, but it is optional as well.
ComplexDiagram

Syntax diagrams

The SDEditGen syntax is described in the form of railroad diagrams generated by RRD for ANTLR4 tool. The following excerpt illustrates how they look:

SyntaxExcerpt

Running

SDEditGen currently doesn’t include a graphical user interface. There are two ways of converting the source SDEDitGen code to sdedit format: an HTTP server and a command-line interface.

HTTP server

An HTTP server which converts from SDEDitGen to sdedit format allows us to use GUI of an HTTP client application like Insomnia.

The server can be started from command line using sbt runMain command:

sbt "runMain sdeditgen.Server"

Alternatively, you can first start sbt and then execute runMain:

$ sbt

> runMain sdeditgen.Server

The command starts the server on the port 8080. To convert from SDEditGen source code to sdedit format just issue an HTTP POST request to the following endpoint:

localhost:8080/generate

With Insomnia it looks like this:

Insomnia

Command-line interface

An SDEditGen source file can be converted into sdedit form by the following command:

sbt "runMain sdeditgen.CLI <input file path> <output folder path>"

Both paths can be relative or absolute. If <output folder path> is a relative path it is interpreted as a subfolder of the input folder. If <output folder path> is a dot character the input and output folders are the same.

The input file’s extension must be sdgen.

Let’s see several examples:

sbt "runMain sdeditgen.CLI /users/jsmith/source/demo.sdgen target"

sbt "runMain sdeditgen.CLI /users/jsmith/source/demo.sdgen /users/jsmith/source/target"

sbt "runMain sdeditgen.CLI source/demo.sdgen target"

sbt "runMain sdeditgen.CLI demo.sdgen ."

Of course, an alternative way is to start sbt and execute runMain in sbt shell:

$ sbt

> runMain sdeditgen.CLI <input file path> <output folder path>